tag:blogger.com,1999:blog-175713052024-03-20T19:58:10.606+08:00小克's 部落格懂得不多 但想法很多 喜歡亂想 更樂於分享
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comBlogger47125tag:blogger.com,1999:blog-17571305.post-22896680798581267952023-07-03T09:49:00.001+08:002023-07-03T09:49:07.644+08:00鬧書荒?你可以參考這幾個書單網站繼續買起來!<p>在 2019 年的時候我曾經寫過 <a href="https://blog.goodjack.tw/2019/07/top-mentioned-books-on-stackoverflow.html">[買起來書單] 那些最常在 Stack Overflow 被提到的 30 本工程師必備書籍</a>,當時參考的資料來源是 <a href="https://web.archive.org/web/20191016184238/http://www.dev-books.com/">dev-books</a>,可惜這個網站就只活到了 2019 年就消失了。</p>
<p>那麼還有哪些書單網站可以推坑買書呢?我們分成兩大類來整理一下。</p>
<p><img src="https://images.unsplash.com/photo-1605732021795-68ea025c3d37" alt="Photo by Aleksander Vlad on Unsplash"></p>
<blockquote>
<p>Photo by <a href="https://unsplash.com/@aleksowlade?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Aleksander Vlad</a> on <a href="https://unsplash.com/photos/5IB1fLcHJtk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></p>
</blockquote>
<h2 id="名人推薦">名人推薦</h2>
<p>以下的網站是搜集各種名人的推薦後統計而成,大多都還有特定分類的書單。<br>
這裡附上的以不分類的榜單為主,照個人喜好排序。</p>
<ul>
<li><a href="https://www.goodbooks.io/top-100/all-books">Good Books</a></li>
<li><a href="https://www.mostrecommendedbooks.com/best-books-of-all-time">Most Recommended Books</a></li>
<li><a href="https://www.readthistwice.com/lists/most-recommended-books">Read This Twice</a></li>
<li><a href="https://postmake.io/startup-books">Startup Books – Postmake</a></li>
<li><a href="https://bookauthority.org">BookAuthority</a></li>
</ul>
<h2 id="技術人推薦">技術人推薦</h2>
<p>以下的網站是統計技術論壇中書籍被提及的次數。照個人喜好排序。</p>
<ul>
<li><a href="https://hackernewsbooks.com/top-books-on-hacker-news">Hacker News Books</a></li>
<li><a href="https://toptalkedbooks.com/best">Top Talked Books</a>
<ul>
<li><a href="https://toptalkedbooks.com/hackernews/all">from Hacker News</a></li>
<li><a href="https://toptalkedbooks.com/stackoverflow/all">from Stack Overflow</a></li>
<li><a href="https://toptalkedbooks.com/reddit/all">from Reddit</a></li>
</ul>
</li>
<li><a href="https://hacker-recommended-books.vercel.app/">HackerNews Readings</a>(資料好像停在 2021 了)</li>
</ul>
<br />
<br />
<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license"><img alt="創用 CC 授權條款" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" style="border-width: 0;" /></a><br />
本著作由<a href="https://goodjack.blogspot.com/" property="cc:attributionName" rel="cc:attributionURL" xmlns:cc="http://creativecommons.org/ns#">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license">創用CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-38444224266583008962023-04-26T01:38:00.001+08:002023-04-26T01:38:16.867+08:00升級 PHP 8/8.1 後值得調整的一些新寫法<p>升級 PHP 8/8.1 後,用 PHP CS Fixer 的 Migration 系列 rule set,看看原本專案內哪些部分有新的寫法,整理一篇記錄一下。</p>
<p><img src="https://i.imgur.com/J55dChd.jpg" alt=""></p>
<h2 id="更直覺的-str_contains-和-str_starts_with">更直覺的 <code>str_contains</code> 和 <code>str_starts_with</code></h2>
<blockquote>
<p>相關規則:<a href="https://cs.symfony.com/doc/rules/alias/modernize_strpos.html"><code>modernize_strpos</code></a></p>
</blockquote>
<p>以往我們常使用 <a href="https://www.php.net/manual/zh/function.strpos.php"><code>strpos</code></a> 來檢查指定字串是否有包含另一個字串,或者用它來判斷字串的開始:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">strpos</span><span class="token punctuation">(</span><span class="token string">'Foo Bar Baz'</span><span class="token punctuation">,</span> <span class="token string">'Foo'</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">echo</span> <span class="token string">'Found'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">strpos</span><span class="token punctuation">(</span><span class="token string">'Foo Bar Baz'</span><span class="token punctuation">,</span> <span class="token string">'Foo'</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">echo</span> <span class="token string">'Start with Foo'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>現在有簡單的 <a href="https://www.php.net/manual/en/function.str-contains.php"><code>str_contains</code></a> 和 <a href="https://www.php.net/manual/en/function.str-starts-with.php"><code>str_starts_with</code></a> 了,可讀性更佳:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">str_contains</span><span class="token punctuation">(</span><span class="token string">'Foo Bar Baz'</span><span class="token punctuation">,</span> <span class="token string">'Foo'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">echo</span> <span class="token string">'Found'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">str_starts_with</span><span class="token punctuation">(</span><span class="token string">'Foo Bar Baz'</span><span class="token punctuation">,</span> <span class="token string">'Foo'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">echo</span> <span class="token string">'Start with Foo'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<h2 id="更方便的-class--magic-constants">更方便的 <code>::class</code> magic constants</h2>
<blockquote>
<p>相關規則:<a href="https://cs.symfony.com/doc/rules/language_construct/get_class_to_class_keyword.html"><code>get_class_to_class_keyword</code></a></p>
</blockquote>
<p>以往 <a href="https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.class"><code>::class</code></a> 只能用在 Class 名稱後方,想要從 object 取得 Class 名稱需要使用 <a href="https://www.php.net/manual/en/function.get-class.php"><code>get_class</code></a>。</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$object</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ClassName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">echo</span> <span class="token function">get_class</span><span class="token punctuation">(</span><span class="token variable">$object</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>現在可以直接使用在 object 上了:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$object</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ClassName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">echo</span> <span class="token variable">$object</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token keyword">class</span><span class="token punctuation">;</span>
</code></pre>
<h2 id="更好讀的八進位數字">更好讀的八進位數字</h2>
<blockquote>
<p>相關規則:<a href="https://cs.symfony.com/doc/rules/language_construct/get_class_to_class_keyword.html"><code>octal_notation</code></a></p>
</blockquote>
<p>PHP 8.1 開始支援以 <a href="https://www.php.net/manual/en/language.types.integer.php"><code>0o</code></a> 前綴來標示八進位的數字,讓可讀性又進一步提升了。</p>
<pre class=" language-php"><code class="prism language-php"><span class="token number">0644</span> <span class="token operator">===</span> 0o644 <span class="token comment">// true</span>
</code></pre>
<h2 id="更安全的-random_int">更安全的 <code>random_int</code></h2>
<blockquote>
<p>相關規則:<a href="https://cs.symfony.com/doc/rules/alias/random_api_migration.html"><code>random_api_migration</code></a></p>
</blockquote>
<p>PHP 7.0 開始就內建的 <a href="https://www.php.net/manual/en/function.random-int.php"><code>random_int</code></a>,一直是官方推薦要採用的亂數函式,比起過去常見的 <a href="https://www.php.net/manual/en/function.rand.php"><code>rand</code></a> 更安全。</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">echo</span> <span class="token function">random_int</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<h2 id="參考資料">參考資料</h2>
<ul>
<li><a href="https://php.watch/versions/8.0">PHP 8.0: What’s New and Changed • PHP.Watch</a></li>
<li><a href="https://php.watch/versions/8.1">PHP 8.1: What’s New and Changed • PHP.Watch</a></li>
</ul>
<br />
<br />
<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license"><img alt="創用 CC 授權條款" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" style="border-width: 0;" /></a><br />
本著作由<a href="https://goodjack.blogspot.com/" property="cc:attributionName" rel="cc:attributionURL" xmlns:cc="http://creativecommons.org/ns#">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license">創用CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-21201790957124302972023-01-29T10:46:00.001+08:002023-01-29T10:46:33.909+08:00寫 Web 也可以用 Makefile:好好管理你的環境流程<p>說到跟別人一起程式專案的協作,我們要解決三個部分的共享:程式碼、環境、流程。程式碼我們大多使用 Git 來整合來自多方的程式碼貢獻,我們也已經盡可能地用 Docker 來解決執行環境的問題。那流程呢?我們常用的一些腳本跟流程該如何協作共享?這時候,Make 就派上用場了。</p>
<p><img src="https://i.imgur.com/G1bzgTM.png" alt=""></p>
<blockquote>
<p><a href="https://commons.wikimedia.org/wiki/File:The_GNU_logo.png">The GNU logo</a></p>
</blockquote>
<p><div class="toc">
<ul>
<li>
<ul>
<li><a href="#你需要具備的內容">你需要具備的內容</a></li>
<li><a href="#什麼是-makemakefile">什麼是 Make/Makefile</a></li>
<li><a href="#為什麼要用-make">為什麼要用 Make</a></li>
<li><a href="#hello-world">Hello World</a></li>
<li><a href="#makefile-的主要本體:target">Makefile 的主要本體:Target</a></li>
<li><a href="#prerequisite-與-phony-target">Prerequisite 與 Phony Target</a></li>
<li><a href="#來點變數">來點變數</a></li>
<li><a href="#做一些條件判斷">做一些條件判斷</a></li>
<li><a href="#控制字串的輸出">控制字串的輸出</a></li>
<li><a href="#組合技:管理不同環境的流程">組合技:管理不同環境的流程</a></li>
<li><a href="#克外補充:進階用法分享">克外補充:進階用法分享</a>
<ul>
<li><a href="#註解的使用">註解的使用</a></li>
<li><a href="#取得當前-target-名">取得當前 Target 名</a></li>
<li><a href="#make-變數與-shell-script-變數混用">Make 變數與 Shell Script 變數混用</a></li>
<li><a href="#變數內容可以為-shell-執行結果">變數內容可以為 Shell 執行結果</a></li>
<li><a href="#變數可以擴充">變數可以擴充</a></li>
<li><a href="#想要抽成-function?">想要抽成 function?</a></li>
<li><a href="#剛提到了-info、shell-和-call,還有沒有其他神奇-function">剛提到了 info、shell 和 call,還有沒有其他神奇 Function</a></li>
<li><a href="#如果想要中間才執行-prerequisite?">如果想要中間才執行 Prerequisite?</a></li>
<li><a href="#更多-prerequisite-用法">更多 Prerequisite 用法</a></li>
<li><a href="#進階變數使用">進階變數使用</a></li>
</ul>
</li>
<li><a href="#結語">結語</a></li>
</ul>
</li>
</ul>
</div></p>
<h2 id="你需要具備的內容">你需要具備的內容</h2>
<p>閱讀本篇文章,你可能需要具備以下內容,並依據實際所需調整範例:</p>
<ul>
<li>包含有 Make 的環境(macOS 和多數 Linux 發行版皆有內建)</li>
<li>能使用 Docker 與 Docker Compose 基本指令</li>
<li>能使用 Shell 基本語法與指令</li>
<li>瞭解 .env 檔(dotEnv)的使用情境</li>
<li>能區分 Tab 字元與空白字元的差別</li>
</ul>
<h2 id="什麼是-makemakefile">什麼是 Make/Makefile</h2>
<p>如果你曾接觸過 Linux 軟體開發,應該對 Make 不陌生。Make 是一個 Cli 工具,透過 Makefile 檔案設計一系列的流程,讓我們執行單一指令就能自動化完成一連串的目的。Make 很常使用在 C/C++ 等編譯語言的軟體,方便其他使用者直接執行開發者設計好的編譯、測試、封裝等流程。</p>
<p>另外,雖然他叫做 Make,但由於 make 本身也是英文動詞,如果要描述或找資料的話,通常也會習慣稱呼其為 Makefile,或是 GNU Make。</p>
<h2 id="為什麼要用-make">為什麼要用 Make</h2>
<p>也許你有疑問,那我們直接寫 Shell Script 是不是也可以達到類似目的呢?</p>
<p>如果你只有簡單幾個流程,那也許 Shell Script 就已足夠。但透過 Make,我們可以重複使用類似的邏輯,只專注不同情境或環境下的差異。</p>
<p>這篇文章不是專業的 Make 教學文,但是會整理一些我在 web 開發時常見的 Make 用法,你可以看看 Make 是不是適合你的工具,也歡迎大家跟我分享更多實用的技巧和做法。</p>
<h2 id="hello-world">Hello World</h2>
<p>首先我們以顯示 Hello World 為目標,可以撰寫一個名為 makefile 的檔案,內容如下:</p>
<pre class=" language-makefile"><code class="prism language-makefile"><span class="token symbol">hello</span><span class="token punctuation">:</span>
echo <span class="token string">"Hello World"</span>
</code></pre>
<p><strong>注意:Makefile 的縮排應使用 Tab,否則會出現語法問題。</strong></p>
<p>這就是最基本的 Makefile 架構了,接著我們在該目錄執行 <code>make hello</code>,此時應該能看到以下內容:</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token keyword">echo</span> <span class="token string">"Hello World"</span>
Hello World
</code></pre>
<p>Makefile 主要是由各種規則(<a href="https://www.gnu.org/software/make/manual/html_node/Rule-Syntax.html">Rule</a>)組成,可以細分為作為名稱的「Target」以及規則內容的「Recipe」。以本例來說,「hello」就是 Target,「echo “Hello World”」就是要做什麼事情的 Recipe。在口語上,我們也會直接將「Rule」稱作「Target」。</p>
<p>每個 Target 可以透過 <code>make [target]</code> 來執行,例如本例 <code>make hello</code> 就可以幫我們顯示 Hello World 字樣,過程中執行了什麼指令也會一併顯示在終端機中。</p>
<h2 id="makefile-的主要本體:target">Makefile 的主要本體:Target</h2>
<p>我們再舉個跟 Docker Compose 指令搭配的例子,撰寫一個名為 makefile 的檔案,放在 docker-compose.yml 的同級目錄:</p>
<pre class=" language-makefile"><code class="prism language-makefile"><span class="token symbol">up</span><span class="token punctuation">:</span>
docker compose up -d workspace
<span class="token symbol">stop</span><span class="token punctuation">:</span>
docker compose stop
<span class="token symbol">zsh</span><span class="token punctuation">:</span>
docker compose exec workspace zsh
</code></pre>
<p>本例有三個 Target:<code>up</code>、<code>stop</code>、<code>zsh</code>。Makefile 預設將第一個 Target 視為 <a href="https://www.gnu.org/software/make/manual/html_node/Goals.html">Goal</a>,是專案的最主要流程,可以直接用 <code>make</code> 執行。以本例來說,執行 <code>make</code> 和 <code>make up</code> 是一樣的結果。</p>
<p>Target 可以支援多行,空白行以前都是同一個 Target 的範圍。例如我們想在每次啟動主流程前都先複製 .env.example 到 .env,可以改寫成這樣:</p>
<pre class=" language-makefile"><code class="prism language-makefile"><span class="token symbol">up</span><span class="token punctuation">:</span>
cp .env.example .env
docker compose up -d workspace
<span class="token symbol">stop</span><span class="token punctuation">:</span>
docker compose stop
<span class="token symbol">zsh</span><span class="token punctuation">:</span>
docker compose exec workspace zsh
</code></pre>
<h2 id="prerequisite-與-phony-target">Prerequisite 與 Phony Target</h2>
<p>但其實剛剛複製檔案的例子不是常見的 Make 用法。Make 的強項是在自動判斷有沒有必要執行每個 Target 的流程。例如我們常常將機敏資料放在 .env 中,若 .env 已經存在,就不應該再複製 .env.example 覆寫過去了。這時候我們可以把 .env 做成一個 Target:</p>
<pre class=" language-makefile"><code class="prism language-makefile"><span class="token symbol">up</span><span class="token punctuation">:</span> .env
docker compose up -d workspace
<span class="token symbol">.env</span><span class="token punctuation">:</span>
cp .env.example .env
</code></pre>
<p>此時當我們執行 <code>up</code> Target 時,會先去執行冒號右方的每一個 Target,稱為先決條件(Prerequisite,以本例來說是 <code>.env</code> Target),接著才執行 <code>up</code> 本身。</p>
<p>Target 名稱預設是被視為檔名的。Make 之所以稱為 make,就是想要「製作」出指定的 Target,當符合指定條件時(如檔案不存在)才會執行 Target 的內容。</p>
<p>以本例來說,我們執行 <code>up</code> Target 時,如果 .env 不存在,就會先執行 <code>.env</code> Target 以複製出 .env,接著才會啟動 workspace container。如果執行 <code>up</code> Target 時 .env 已經存在,就會略過 <code>.env</code> Target,直接啟動 workspace container。</p>
<p>同樣地,如果我們目錄中有「up」這個檔案, <code>up</code> Target 就不會被執行了。這時我們可以設定 <a href="https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html">Phony Target</a>,告訴 Make 哪些 Target 不是檔案的名稱,而是單純流程的命名。寫法如下:</p>
<pre class=" language-makefile"><code class="prism language-makefile"><span class="token builtin">.PHONY</span><span class="token punctuation">:</span> up stop zsh
<span class="token symbol">up</span><span class="token punctuation">:</span> .env
docker compose up -d workspace
<span class="token symbol">stop</span><span class="token punctuation">:</span>
docker compose stop
<span class="token symbol">zsh</span><span class="token punctuation">:</span>
docker compose exec workspace zsh
<span class="token symbol">.env</span><span class="token punctuation">:</span>
cp .env.example .env
</code></pre>
<p>第一行看到的 <code>.PHONY</code> 是 Make 的保留字,告訴 Make 這些是不該被跳過執行的 Target,我們在這裡列了 <code>up</code>、<code>stop</code>、<code>zsh</code> 三個 Target。當我們未來執行這三個 Target 時,就不會因為檔名不小心等同於這些 Target 而被跳過,影響到流程了。</p>
<p>順帶一提,上一段提過 Make 會將第一個 Target 視為 Goal。但 Goal 其實還規定了不能是點(dot)開頭的 Target,所以此例的 Goal 依然是 <code>up</code> Target。</p>
<p>隨著專案的流程越複雜,也許我們會有越多的 Phony Target,這將會使 <code>.PHONY</code> 變得很長,因此 Make 支援多個 <code>.PHONY</code> 的表示法:</p>
<pre class=" language-makefile"><code class="prism language-makefile"><span class="token symbol">up</span><span class="token punctuation">:</span> .env
docker compose up -d workspace
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> up
<span class="token symbol">stop</span><span class="token punctuation">:</span>
docker compose stop
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> stop
<span class="token symbol">zsh</span><span class="token punctuation">:</span>
docker compose exec workspace zsh
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> zsh
<span class="token symbol">.env</span><span class="token punctuation">:</span>
cp .env.example .env
</code></pre>
<p>這樣直接寫在 Target 附近的寫法,可以一眼就看出來哪個 Target 是 Phony Target,大幅提升可讀性跟可維護性。</p>
<h2 id="來點變數">來點變數</h2>
<p>Make 當然也支援變數(<a href="https://www.gnu.org/software/make/manual/html_node/Setting.html">Variable</a>),與常見的 Unix 環境變數慣例相同,我們習慣用 SCREAMING_SNAKE_CASE 表示法(全大寫和底線的表示法)。並且在使用時以 <code>$()</code> 包裹變數名稱。</p>
<p>例如我們想要方便啟動指定的 container,可以將 <code>up</code> 改寫成:</p>
<pre class=" language-makefile"><code class="prism language-makefile">CONTAINERS <span class="token operator">?=</span> workspace mysql
<span class="token symbol">up</span><span class="token punctuation">:</span>
docker compose up -d <span class="token variable">$</span><span class="token punctuation">(</span>CONTAINERS<span class="token punctuation">)</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> up
</code></pre>
<p>這裡我們設定了一個變數 <code>CONTAINERS</code>,當我們未指定時,預設值為 「workspace mysql」。例如我們呼叫 <code>make up</code> 時會執行以下指令:</p>
<pre class=" language-bash"><code class="prism language-bash">docker compose up -d workspace mysql
</code></pre>
<p>當我們想要給予該變數一個值,例如我們想用 <code>up</code> Target 開啟 redis container,可以這樣呼叫:</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token function">make</span> up CONTAINERS<span class="token operator">=</span><span class="token string">"redis"</span>
</code></pre>
<p>這時 Make 就會幫我們執行以下指令:</p>
<pre class=" language-bash"><code class="prism language-bash">docker compose up -d redis
</code></pre>
<p>另一種常見的用法是透過變數指定 Docker Compose 的參數,如以下範例:</p>
<pre class=" language-makefile"><code class="prism language-makefile">CONTAINER_USER <span class="token operator">?=</span> default
CONTAINERS <span class="token operator">?=</span> workspace
<span class="token symbol">zsh</span><span class="token punctuation">:</span>
docker compose exec --user<span class="token operator">=</span><span class="token variable">$</span><span class="token punctuation">(</span>CONTAINER_USER<span class="token punctuation">)</span> <span class="token variable">$</span><span class="token punctuation">(</span>CONTAINERS<span class="token punctuation">)</span> zsh
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> zsh
</code></pre>
<p>這裡預設是以「default」來進入 container。這時我們可以透過指定 <code>CONTAINER_USER</code> 來更改執行指令的使用者,以指定成「root」為例:</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token function">make</span> zsh CONTAINER_USER<span class="token operator">=</span><span class="token string">"root"</span>
</code></pre>
<p>這時 Make 就會幫我們執行以下指令,以「root」進入 container:</p>
<pre class=" language-bash"><code class="prism language-bash">docker compose <span class="token function">exec</span> --user<span class="token operator">=</span>root workspace zsh
</code></pre>
<h2 id="做一些條件判斷">做一些條件判斷</h2>
<p>想要有一些稍微複雜的邏輯判斷?Make 也支援條件式(<a href="https://www.gnu.org/software/make/manual/html_node/Conditional-Syntax.html">Conditional</a>),最常見的是 <code>ifeq</code> 和 <code>ifneq</code>,分別對應「如果等於」和「如果不等於」,以下是範例:</p>
<pre class=" language-makefile"><code class="prism language-makefile">IS_ROOT <span class="token operator">?=</span> false
<span class="token symbol">zsh</span><span class="token punctuation">:</span>
<span class="token keyword">ifeq</span> <span class="token punctuation">(</span><span class="token variable">$</span><span class="token punctuation">(</span>IS_ROOT<span class="token punctuation">)</span>, true<span class="token punctuation">)</span>
docker compose exec --user<span class="token operator">=</span>root workspace zsh
<span class="token keyword">else</span>
docker compose exec workspace zsh
<span class="token keyword">endif</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> zsh
</code></pre>
<p>以此例來說,當我們執行 <code>make zsh</code> 時,Make 會判斷 <code>$(IS_ROOT)</code> 是否等於 “true”,若相等的話,就會以 root 的身份進入 workspace container,否則就改以預設的 user 進入。</p>
<p>首先要注意的是,只有被執行的指令部分需要 Tab 縮排,條件式相關的語句應該要保持不縮排,因為他是屬於 Make 語法的一部分。另外提醒,雖然本例中使用的是 true/false,但其實 Make 是沒有布林值型態的,在這裡是比對字串有無相等。</p>
<h2 id="控制字串的輸出">控制字串的輸出</h2>
<p>接著我們來加一些輸出,讓我們能更容易辨識流程。以下是 Make 標準輸出的 <a href="https://www.gnu.org/software/make/manual/html_node/Make-Control-Functions.html">Control Function</a>,稱之為 <code>info</code>:</p>
<pre class=" language-makefile"><code class="prism language-makefile">IS_ROOT <span class="token operator">?=</span> false
<span class="token symbol">zsh</span><span class="token punctuation">:</span>
<span class="token keyword">ifeq</span> <span class="token punctuation">(</span><span class="token variable">$</span><span class="token punctuation">(</span>IS_ROOT<span class="token punctuation">)</span>, true<span class="token punctuation">)</span>
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 以 Root 身份進入 workspace<span class="token punctuation">)</span>
docker compose exec --user<span class="token operator">=</span>root workspace zsh
<span class="token keyword">else</span>
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 以預設身份進入 workspace<span class="token punctuation">)</span>
docker compose exec workspace zsh
<span class="token keyword">endif</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> zsh
</code></pre>
<p>此時若我們執行 <code>make zsh</code>,看到的輸出如下:</p>
<pre class=" language-bash"><code class="prism language-bash">以預設身份進入 workspace
docker compose <span class="token function">exec</span> workspace zsh
<span class="token comment"># 接者是 Docker Compose 執行結果</span>
</code></pre>
<p>如此透過 Control Function 我們就能更客製化顯示的內容,另外還有 <code>warning</code> 和 <code>error</code> 兩種輸出,可以參考說明文件。</p>
<p>另外,有時不想要我們的指令干擾畫面的呈現,這時候我們可以在行首加上 <code>@</code> 符號,阻止 Make <a href="https://www.gnu.org/software/make/manual/html_node/Echoing.html">Echoing</a>。以文章開始的 Hello World 範例改寫如下:</p>
<pre class=" language-makefile"><code class="prism language-makefile"><span class="token symbol">hello</span><span class="token punctuation">:</span>
<span class="token operator">@</span>echo <span class="token string">"Hello World"</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> hello
</code></pre>
<p>這時當我們執行 <code>make hello</code>,呈現的結果如下:</p>
<pre class=" language-bash"><code class="prism language-bash">Hello World
</code></pre>
<p>就不會出現 <code>echo "Hello World"</code> 字樣了。</p>
<h2 id="組合技:管理不同環境的流程">組合技:管理不同環境的流程</h2>
<p>讀到這裡,我們已經掌握了 Make 的基本用法。接者我們來討論看看該怎麼管理不同環境的流程。</p>
<p>假設我們分成「開發環境(dev)」與「正式環境(production)」,啟動專案的流程如下:</p>
<ul>
<li>兩者環境啟動前都需要 .env 檔,若檔案不存在,dev 環境從 .env.example 複製建立,production 環境從 .env.example.production 複製建立</li>
<li>兩者環境都需要啟動 workspace container,dev 環境還要額外啟用 redis container</li>
<li>啟動時呈現當前環境名稱</li>
<li>啟動流程不顯示指令,但以中文描述動作</li>
</ul>
<p>以下為其中一種 Makefile 寫法:</p>
<pre class=" language-makefile"><code class="prism language-makefile">ENVIRONMENT <span class="token operator">?=</span> dev
<span class="token symbol">up</span><span class="token punctuation">:</span> .env
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 目前環境為 <span class="token variable">$</span><span class="token punctuation">(</span>ENVIRONMENT<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 啟動 workspace<span class="token punctuation">)</span>
docker compose up -d workspace
<span class="token keyword">ifeq</span> <span class="token punctuation">(</span><span class="token variable">$</span><span class="token punctuation">(</span>ENVIRONMENT<span class="token punctuation">)</span>, dev<span class="token punctuation">)</span>
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 啟動 redis<span class="token punctuation">)</span>
docker compose up -d redis
<span class="token keyword">endif</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> up
<span class="token symbol">.env</span><span class="token punctuation">:</span>
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> .env 不存在,建立 .env 檔<span class="token punctuation">)</span>
<span class="token keyword">ifeq</span> <span class="token punctuation">(</span><span class="token variable">$</span><span class="token punctuation">(</span>ENVIRONMENT<span class="token punctuation">)</span>, dev<span class="token punctuation">)</span>
cp .env.example .env
<span class="token keyword">else</span>
cp .env.example.production .env
<span class="token keyword">endif</span>
</code></pre>
<p>到這裡,我們已經可以開始撰寫針對不同環境的流程了!我們還可以加上一些 Phony Target 來整理常用的指令,例如前面範例提過的 <code>stop</code> 和 <code>zsh</code>,或是執行 Laravel 測試:</p>
<pre class=" language-makefile"><code class="prism language-makefile"><span class="token symbol">test</span><span class="token punctuation">:</span>
docker compose exec workspace php artisan test
<span class="token builtin">.PHONE</span><span class="token punctuation">:</span> test
</code></pre>
<hr>
<h2 id="克外補充:進階用法分享">克外補充:進階用法分享</h2>
<p>由於本篇只是分享我是如何透過 Make/Makefile 管理流程,進階用法就以簡單的方式條列分享,也歡迎將你的使用方式或經驗分享給我!</p>
<h3 id="註解的使用">註解的使用</h3>
<p>跟 Shell Script 一樣使用 <code>#</code>。</p>
<p>值得注意的是,如果在 Target 中使用 Tab 縮排後的 <code>#</code> ,會被視為是 Shell Script 的註解。</p>
<h3 id="取得當前-target-名">取得當前 Target 名</h3>
<p>使用 <code>$@</code>。</p>
<pre class=" language-makefile"><code class="prism language-makefile"><span class="token symbol">up</span><span class="token punctuation">:</span>
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 目前執行的 Target 是 <span class="token variable">$@</span><span class="token punctuation">)</span> <span class="token comment"># 顯示 up</span>
docker compose up -d <span class="token variable">$</span><span class="token punctuation">(</span>CONTAINERS<span class="token punctuation">)</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> up
</code></pre>
<h3 id="make-變數與-shell-script-變數混用">Make 變數與 Shell Script 變數混用</h3>
<p>也許你在流程中想使用 Shell Script 變數,如果要使用 <code>$</code> 在指令中,跳脫的方法不是 <code>\$</code>,而是 <code>$$</code>。</p>
<h3 id="變數內容可以為-shell-執行結果">變數內容可以為 Shell 執行結果</h3>
<p>請看範例:</p>
<pre class=" language-makefile"><code class="prism language-makefile">MY_IP <span class="token operator">=</span> <span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">shell</span> curl -s ipinfo.io/ip<span class="token punctuation">)</span>
<span class="token symbol">get-ip</span><span class="token punctuation">:</span>
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 我的 IP:<span class="token variable">$</span><span class="token punctuation">(</span>MY_IP<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> get-ip
</code></pre>
<h3 id="變數可以擴充">變數可以擴充</h3>
<p>透過 <code>+=</code> 和 <code>ifeq</code> 可以更簡單的管理環境,請看範例:</p>
<pre class=" language-makefile"><code class="prism language-makefile">ENVIRONMENT <span class="token operator">?=</span> dev
CONTAINERS <span class="token operator">?=</span> workspace
<span class="token keyword">ifeq</span> <span class="token punctuation">(</span><span class="token variable">$</span><span class="token punctuation">(</span>ENVIRONMENT<span class="token punctuation">)</span>, dev<span class="token punctuation">)</span>
<span class="token comment"># 強制 dev 環境會開啟 redis</span>
CONTAINERS <span class="token operator">+=</span> redis
<span class="token keyword">endif</span>
<span class="token symbol">up</span><span class="token punctuation">:</span>
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 目前環境為 <span class="token variable">$</span><span class="token punctuation">(</span>ENVIRONMENT<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 啟動 <span class="token variable">$</span><span class="token punctuation">(</span>CONTAINERS<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token comment"># dev 環境預設會開啟 workspace 和 redis</span>
docker compose up -d <span class="token variable">$</span><span class="token punctuation">(</span>CONTAINERS<span class="token punctuation">)</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> up
</code></pre>
<h3 id="想要抽成-function?">想要抽成 function?</h3>
<p>如果有一直重複的指令前綴可以抽成變數,參數也可以抽成另一個變數方便執行時替換:</p>
<pre class=" language-makefile"><code class="prism language-makefile">COMPOSE_FLAGS <span class="token operator">?=</span> -d
EXEC_CONTAINER <span class="token operator">?=</span> workspace
EXEC <span class="token operator">?=</span> docker compose exec <span class="token variable">$</span><span class="token punctuation">(</span>COMPOSE_FLAGS<span class="token punctuation">)</span> <span class="token variable">$</span><span class="token punctuation">(</span>EXEC_CONTAINER<span class="token punctuation">)</span>
<span class="token symbol">zsh</span><span class="token punctuation">:</span>
<span class="token variable">$</span><span class="token punctuation">(</span>EXEC<span class="token punctuation">)</span> zsh
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> zsh
<span class="token symbol">bash</span><span class="token punctuation">:</span>
<span class="token variable">$</span><span class="token punctuation">(</span>EXEC<span class="token punctuation">)</span> bash
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> bash
</code></pre>
<p>我們也許可以這樣執行:</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token function">make</span> zsh COMPOSE_FLAGS<span class="token operator">=</span><span class="token string">"-d -T"</span>
</code></pre>
<p>當然 flags 也可以用前面提到的 <code>+=</code> 概念去組合。</p>
<p>除了一般變數的用法外,還有多行變數(<a href="https://www.gnu.org/software/make/manual/html_node/Multi_002dLine.html"><code>define</code></a>)搭配 <a href="https://www.gnu.org/software/make/manual/html_node/Call-Function.html">Call Function</a> <code>$(call [variable])</code> 的用法。</p>
<h3 id="剛提到了-info、shell-和-call,還有沒有其他神奇-function">剛提到了 info、shell 和 call,還有沒有其他神奇 Function</h3>
<p>還蠻多的,例如 filter、subst、realpath⋯⋯。想看各種 Function 的介紹請參考 <a href="https://www.gnu.org/software/make/manual/html_node/Functions.html">Function</a> 說明文件。</p>
<p>另外,所有 Make 內建的 Function、變數、指令可以 <a href="https://www.gnu.org/software/make/manual/html_node/Name-Index.html">在此查表</a>。</p>
<h3 id="如果想要中間才執行-prerequisite?">如果想要中間才執行 Prerequisite?</h3>
<p>可以使用 <a href="https://www.gnu.org/software/make/manual/html_node/Double_002dColon.html">Double-Colon Rules</a> 語法,主要是把 <code>:</code> 改成 <code>::</code>,將 Target 拆開成兩部分,例如:</p>
<pre class=" language-makefile"><code class="prism language-makefile"><span class="token symbol">up</span><span class="token punctuation">:</span><span class="token punctuation">:</span>
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 我先顯示這句後才想製作 .env<span class="token punctuation">)</span>
<span class="token symbol">up</span><span class="token punctuation">:</span><span class="token punctuation">:</span> .env
<span class="token variable">$</span><span class="token punctuation">(</span><span class="token keyword">info</span> 製作 .env 後才啟動 workspace<span class="token punctuation">)</span>
docker compose up -d workspace
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> up
<span class="token symbol">.env</span><span class="token punctuation">:</span>
cp .env.example .env
</code></pre>
<h3 id="更多-prerequisite-用法">更多 Prerequisite 用法</h3>
<p>可以使用變數決定 Prerequisite,Target 也可以是路徑:</p>
<pre class=" language-makefile"><code class="prism language-makefile">PREREQUISITE <span class="token operator">?=</span> .env ../laravel/.env
<span class="token symbol">up</span><span class="token punctuation">:</span> <span class="token variable">$</span><span class="token punctuation">(</span>PREREQUISITE<span class="token punctuation">)</span>
docker compose up -d workspace
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> up
<span class="token symbol">.env</span><span class="token punctuation">:</span>
<span class="token comment"># Docker 的 .env</span>
cp .env.example .env
<span class="token symbol">../laravel/.env</span><span class="token punctuation">:</span>
<span class="token comment"># 隔壁目錄的 .env</span>
cp ../laravel/.env.example ../laravel/.env
</code></pre>
<h3 id="進階變數使用">進階變數使用</h3>
<p>變數宣告另外還有 <code>=</code>、<code>:=</code> 等用法。</p>
<p>另外 Target 是可以給定值的,要附在 Target 前(請見範例)。但這種寫法我覺得維護上有很多問題,我都盡量避免使用。</p>
<p>Makefile 範例一:</p>
<pre class=" language-makefile"><code class="prism language-makefile">TEXT <span class="token operator">?=</span> default
<span class="token symbol">hello</span><span class="token punctuation">:</span> hey
<span class="token symbol"> <span class="token variable">$</span>(info hello</span><span class="token punctuation">:</span> <span class="token variable">$</span><span class="token punctuation">(</span>TEXT<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> hello
<span class="token symbol">hey</span><span class="token punctuation">:</span> TEXT <span class="token operator">?=</span> hey
<span class="token symbol">hey</span><span class="token punctuation">:</span>
<span class="token symbol"> <span class="token variable">$</span>(info hey</span><span class="token punctuation">:</span> <span class="token variable">$</span><span class="token punctuation">(</span>TEXT<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> hey
</code></pre>
<p>Makefile 範例一執行結果,hey 認為 <code>TEXT</code> 已經給過值,就不會套用 hey 值:</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token comment"># 執行 make</span>
hey: default
hello: default
<span class="token comment"># 執行 make TEXT=Jack</span>
hey: Jack
hello: Jack
</code></pre>
<p>Makefile 範例二,將 hey 給值由 <code>?=</code> 改為 <code>=</code>:</p>
<pre class=" language-makefile"><code class="prism language-makefile">TEXT <span class="token operator">?=</span> default
<span class="token symbol">hello</span><span class="token punctuation">:</span> hey
<span class="token symbol"> <span class="token variable">$</span>(info hello</span><span class="token punctuation">:</span> <span class="token variable">$</span><span class="token punctuation">(</span>TEXT<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> hello
<span class="token symbol">hey</span><span class="token punctuation">:</span> TEXT <span class="token operator">=</span> hey
<span class="token symbol">hey</span><span class="token punctuation">:</span>
<span class="token symbol"> <span class="token variable">$</span>(info hey</span><span class="token punctuation">:</span> <span class="token variable">$</span><span class="token punctuation">(</span>TEXT<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> hey
</code></pre>
<p>Makefile 範例二執行結果,此時 hello 不受影響:</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token comment"># 執行 make</span>
hey: hey
hello: default
</code></pre>
<p>若在流程中改值, <code>=</code> 的用法是會先展開取得最終結果,才確定整個流程的變數內容是什麼,從頭到尾值都會保持一致,請見範例三。</p>
<p>Makefile 範例三,改成 hello 給值:</p>
<pre class=" language-makefile"><code class="prism language-makefile">TEXT <span class="token operator">?=</span> default
<span class="token symbol">hello</span><span class="token punctuation">:</span> TEXT <span class="token operator">=</span> hello
<span class="token symbol">hello</span><span class="token punctuation">:</span> hey
<span class="token symbol"> <span class="token variable">$</span>(info hello</span><span class="token punctuation">:</span> <span class="token variable">$</span><span class="token punctuation">(</span>TEXT<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> hello
<span class="token symbol">hey</span><span class="token punctuation">:</span>
<span class="token symbol"> <span class="token variable">$</span>(info hey</span><span class="token punctuation">:</span> <span class="token variable">$</span><span class="token punctuation">(</span>TEXT<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token builtin">.PHONY</span><span class="token punctuation">:</span> hey
</code></pre>
<p>Makefile 範例三執行結果,因為是 <code>=</code>,即使 hello 執行順序比較後面,依然影響到前面的 hey 取值:</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token comment"># 執行 make</span>
hey: hello
hello: hello
</code></pre>
<p>Makefile 也有個 <a href="https://www.gnu.org/software/make/manual/html_node/Recursive-Assignment.html">官方範例</a> 說明 <code>=</code> 展開的概念:</p>
<pre class=" language-makefile"><code class="prism language-makefile">foo <span class="token operator">=</span> <span class="token variable">$</span><span class="token punctuation">(</span>bar<span class="token punctuation">)</span>
bar <span class="token operator">=</span> <span class="token variable">$</span><span class="token punctuation">(</span>ugh<span class="token punctuation">)</span>
ugh <span class="token operator">=</span> Huh?
<span class="token symbol">all</span><span class="token punctuation">:</span>
<span class="token operator">@</span>echo <span class="token variable">$</span><span class="token punctuation">(</span>foo<span class="token punctuation">)</span> <span class="token comment"># 輸出結果為 Huh?</span>
</code></pre>
<p><code>:=</code> 的用法與一般程式語言的等號賦值比較類似。讓我們修改一下剛剛的官方範例,將 <code>=</code> 改成 <code>:=</code>:</p>
<pre class=" language-makefile"><code class="prism language-makefile">foo <span class="token operator">:=</span> <span class="token variable">$</span><span class="token punctuation">(</span>bar<span class="token punctuation">)</span>
bar <span class="token operator">:=</span> <span class="token variable">$</span><span class="token punctuation">(</span>ugh<span class="token punctuation">)</span>
ugh <span class="token operator">:=</span> Huh?
<span class="token symbol">all</span><span class="token punctuation">:</span>
<span class="token operator">@</span>echo <span class="token variable">$</span><span class="token punctuation">(</span>foo<span class="token punctuation">)</span> <span class="token comment"># 輸出結果為空白行</span>
</code></pre>
<h2 id="結語">結語</h2>
<p>感謝你看到這裡,以上是我使用 Make/Makefile 管理流程的做法。歡迎跟我分享交流,你是怎麼管理你的流程呢?</p>
<br />
<br />
<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license"><img alt="創用 CC 授權條款" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" style="border-width: 0;" /></a><br />
本著作由<a href="https://goodjack.blogspot.com/" property="cc:attributionName" rel="cc:attributionURL" xmlns:cc="http://creativecommons.org/ns#">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license">創用CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-47414451216948444842022-11-05T19:33:00.001+08:002022-11-05T19:33:57.267+08:00那些值得裝裝看的酷酷 CLI 工具<p>好久沒寫部落格文了,是時候該振作提起鍵盤寫一寫。<br>
這篇的起因其實是我裝了這些 CLI tool 但都忘記用 😅</p>
<p><img src="https://images.pexels.com/photos/5240547/pexels-photo-5240547.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="Free Crop hacker silhouette typing on computer keyboard while hacking system Stock Photo"></p>
<h2 id="現代-cli-類">現代 CLI 類</h2>
<ul>
<li>
<p><strong><a href="https://github.com/sharkdp/bat">bat</a></strong><br>
取代 cat 的工具,包含了語法 Highlight 和 Git 狀態顯示,顯示文件真的可以又快又漂亮,基於 rust。</p>
<p>其實平常也不常用 cat 啦,還是習慣用 <a href="https://zh.wikipedia.org/wiki/Less_%28Unix%29">less</a> ,搭配 <a href="https://superuser.com/questions/71588/how-to-syntax-highlight-via-less/71593#71593">source-highlight</a> 也能有一定程度的 Highlight(但也就堪用而已,最後還是打開 vim)。</p>
</li>
<li>
<p><strong><a href="https://github.com/httpie/httpie">httpie</a></strong><br>
與其說是現代版的 Curl/Wget,我覺得講 CLI 版的 Postman 會更貼切些。支援 JSON 和 HTML 的語法 Highlight 當然是基本的,還能方便地傳出 JSON API 或 Form data 也是一大賣點。基於 Python。</p>
<p>今年他們也因為掉星星事件有一陣子熱度(<a href="https://httpie.io/blog/stardust">How we lost 54k GitHub stars – HTTPie blog</a>),喜歡的話別忘了幫他們加個星星。</p>
</li>
<li>
<p><strong><a href="https://github.com/ogham/exa">exa</a></strong><br>
現代版 ls。很多漂亮的顏色就是棒,另外還能顯示圖示、Git 狀態,以及提供樹狀顯示等等。基於 rust。</p>
<p>不知道為什麼我的 exa 看起來顏色醜醜的,可能沒有正確吃到 zsh 的設定。</p>
<p>延伸閱讀:<a href="https://ithelp.ithome.com.tw/articles/10272972">11 - exa - 總覽目錄的工具 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天</a></p>
</li>
<li>
<p><strong><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a></strong><br>
取代 grep 的強化版文字搜尋工具,當然也是可以取代 ag、ack。有很多進階功能,例如支援 regex、尊重 ignore 設定、能指定搜尋檔案的範圍或類型等。使用的語法為 <code>rg</code>,基於 rust。</p>
<p>延伸閱讀:<a href="https://ithelp.ithome.com.tw/articles/10271828">09 - ripgrep - 快速查找檔案內容 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天</a></p>
</li>
<li>
<p><strong><a href="https://github.com/sharkdp/fd">fd</a></strong><br>
取代 find,協助你快速找到檔案。一樣是快、漂亮的顏色、支援 Regex、尊重 ignore 設定。基於 rust。</p>
</li>
</ul>
<h2 id="神奇工具類">神奇工具類</h2>
<ul>
<li>
<p><strong><a href="https://github.com/jonas/tig">tig</a></strong><br>
反過來打的 Git,可以在 CLI 看 git 線圖,有人說是 CLI 中的 Git GUI(?</p>
</li>
<li>
<p><strong><a href="https://github.com/github/hub">hub</a></strong><br>
GitHub 官方 CLI 工具。</p>
</li>
<li>
<p><strong><a href="https://github.com/tldr-pages/tldr">tldr</a></strong><br>
社群維護的各種 CLI 工具的常用語法範例。真的很棒很好用。</p>
</li>
<li>
<p><strong><a href="https://github.com/koalaman/shellcheck">shellcheck</a></strong><br>
Shell script 的靜態分析工具,幫你寫出好的 Shell script。</p>
</li>
</ul>
<h2 id="後記">後記</h2>
<p>再搭配一些 zsh 的 plugin 整個終端機應該就非常好用。不過這個很多人寫了,我這裡就簡單列一下就好:</p>
<ul>
<li><a href="https://github.com/zdharma-continuum/zinit">Zinit</a> 套件管理</li>
<li><a href="https://github.com/romkatv/powerlevel10k">Powerlevel10k</a></li>
<li><a href="https://github.com/rupa/z">rupa/z</a></li>
<li><a href="https://github.com/zsh-users/zsh-syntax-highlighting">zsh-syntax-highlighting</a></li>
<li>用 Zinit 啟用一些 Oh my zsh 的套件</li>
</ul>
<p>太久沒寫文章了好累,怎麼感覺虎頭蛇尾啊?<br>
那就歡迎補充交流其他好用工具 ✨</p>
<br />
<br />
<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license"><img alt="創用 CC 授權條款" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" style="border-width: 0;" /></a><br />
本著作由<a href="https://goodjack.blogspot.com/" property="cc:attributionName" rel="cc:attributionURL" xmlns:cc="http://creativecommons.org/ns#">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license">創用CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-87444817868612456582020-05-03T19:18:00.001+08:002020-05-03T19:18:28.746+08:00[延伸創作] 深入 Session 與 Cookie:Laravel 的實作<h2 id="前言">前言</h2>
<p>看到 Huli 寫了這篇 <a href="https://github.com/aszx87410/blog/issues/46">深入 Session 與 Cookie:Express、PHP 與 Rails 的實作</a>,想說既然都寫到了 PHP 和 Rails 了,<s>身為 PHP 版 Rails 的</s> Laravel 怎麼可以缺席呢 (?),所以就也來追追看原始碼吧!</p>
<p>原本想在短時間內就把這篇發出去的,趁著風潮嘛。</p>
<p>結果 Laravel 原始碼果然很難追,Laravel 5.8 版原始碼追著追著 Laravel 6.x 就推出了,擺著擺著 7.x 版又… 🤦♂️,就這樣過了八個多月,看來有得改了,還好差異不大。</p>
<p>那我們就開始吧!</p>
<h2 id="tldr">TL;DR</h2>
<p>可以直接滑最後看結論 😭</p>
<p><div class="toc">
<ul>
<li>
<ul>
<li><a href="#前言">前言</a></li>
<li><a href="#tldr">TL;DR</a></li>
<li><a href="#本篇閱讀指南">本篇閱讀指南</a></li>
<li><a href="#laravel-framework(以-v7.9.2-版為例)">Laravel Framework(以 v7.9.2 版為例)</a></li>
<li><a href="#laravel-怎麼生成-session-id-的">Laravel 怎麼生成 Session ID 的</a>
<ul>
<li><a href="#「重新生成-session-id」">「重新生成 Session ID」</a></li>
<li><a href="#建立-session-id">建立 Session ID</a></li>
<li><a href="#laravel-並不是使用-php-原生的-session-機制">Laravel 並不是使用 PHP 原生的 session 機制</a></li>
</ul>
</li>
<li><a href="#將-session-information-寫進-file">將 Session Information 寫進 File</a>
<ul>
<li><a href="#從-laravel-儲存-session-的指令下手">從 Laravel 儲存 Session 的指令下手</a></li>
<li><a href="#driver:儲存-session-的管道">Driver:儲存 Session 的管道</a></li>
<li><a href="#session-的-service-provider">Session 的 Service Provider</a></li>
<li><a href="#startsession-是個-middleware">StartSession 是個 Middleware</a></li>
<li><a href="#savesession:儲存-session-資料">saveSession():儲存 Session 資料</a></li>
<li><a href="#sessionmanager">SessionManager</a></li>
<li><a href="#managerdriver:取得-driver-instance">Manager::driver():取得 Driver Instance</a></li>
<li><a href="#createfiledriver:建立-file-session-driver-的-instance">createFileDriver():建立 File Session Driver 的 Instance</a></li>
<li><a href="#buildsession-建立-session-instance">buildSession(): 建立 Session Instance</a></li>
<li><a href="#store:session-instance-本體">Store:Session Instance 本體</a></li>
<li><a href="#return-再-return:回到-savesession">return 再 return:回到 saveSession()</a></li>
<li><a href="#save:-儲存-session-資料到指定儲存空間(file)">save(): 儲存 Session 資料到指定儲存空間(File)</a></li>
<li><a href="#write:寫入-session-資料到-file">write():寫入 Session 資料到 File</a></li>
<li><a href="#分岔一下,來講-file-facade">分岔一下,來講 File Facade</a></li>
<li><a href="#回來-write:繼續寫入-session-資料到-file">回來 write():繼續寫入 Session 資料到 File</a></li>
<li><a href="#put:把內容寫入檔案">put():把內容寫入檔案</a></li>
</ul>
</li>
<li><a href="#延伸討論:說好的-cookie-based-session-呢">延伸討論:說好的 cookie-based session 呢</a>
<ul>
<li><a href="#createcookiedriver:建立-cookie-session-driver-的-instance">createCookieDriver():建立 Cookie Session Driver 的 Instance</a></li>
<li><a href="#驚喜地發現,中間流程都一樣!">驚喜地發現,中間流程都一樣!</a></li>
<li><a href="#save:-儲存-session-資料到指定儲存空間(cookie)">save(): 儲存 Session 資料到指定儲存空間(Cookie)</a></li>
<li><a href="#write:寫入-session-資料到-cookie">write():寫入 Session 資料到 Cookie</a></li>
<li><a href="#availableat:取得-session-到期時間的-unix-timestamp">availableAt():取得 Session 到期時間的 UNIX Timestamp</a></li>
<li><a href="#回來-write:繼續寫入-session-資料到-cookie">回來 write():繼續寫入 Session 資料到 Cookie</a></li>
<li><a href="#queue:把內容排入-cookie-的-queue">queue():把內容排入 Cookie 的 Queue</a></li>
</ul>
</li>
<li><a href="#補完最後一塊拼圖:你剛講的都是從一半切入耶">補完最後一塊拼圖:你剛講的都是從一半切入耶</a>
<ul>
<li><a href="#laravel-專案預設的-web-middleware-group">Laravel 專案預設的 web Middleware Group</a></li>
<li><a href="#addqueuedcookiestoresponse:將-cookie-queue-加到-response">AddQueuedCookiesToResponse:將 Cookie Queue 加到 Response</a></li>
</ul>
</li>
<li><a href="#延伸討論:怎樣的字串才是-laravel-的有效-session-id?">延伸討論:怎樣的字串才是 Laravel 的有效 Session ID?</a></li>
<li><a href="#後面來談談:為什麼-laravel-不使用-php-原生的-session-機制">後面來談談:為什麼 Laravel 不使用 PHP 原生的 session 機制</a></li>
<li><a href="#總結">總結</a>
<ul>
<li><a href="#參考資料">參考資料</a></li>
<li><a href="#感謝-huli">感謝 Huli</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div></p>
<h2 id="本篇閱讀指南">本篇閱讀指南</h2>
<ul>
<li>以下內容都盡可能地提供原始碼,並附上在 GitHub 的連結、highlight 對應的行號。</li>
<li>Laravel 在 GitHub 的 repo 分兩種:
<ul>
<li><a href="https://github.com/laravel/laravel/tree/v7.6.0">laravel/laravel</a> 是給使用者建立的專案內容,包含了一些可調整的設定檔。本文以 <code>v7.6.0</code> 版為例。</li>
<li><a href="https://github.com/laravel/framework/tree/v7.9.2">laravel/framework</a> 則是框架原始碼本體。本文以 <code>v7.9.2</code> 版為例。</li>
</ul>
</li>
<li>當本文需要您切換檔案時,會直接於內文提到檔案路徑。<br>
若只是在同一檔案的不同位置,則簡單的附上 GitHub 原始碼超連結。</li>
<li>檔案路徑若標示為 <strong>framework</strong> 開頭,通常位於您專案的 vendor/laravel/framework 下。<br>
其餘檔案路徑則是位於您的專案根目錄下。</li>
</ul>
<h2 id="laravel-framework(以-v7.9.2-版為例)">Laravel Framework(以 <code>v7.9.2</code> 版為例)</h2>
<p>Laravel 除了是目前 PHP 上最熱門的框架,也是截至目前 GitHub 上最多 Star 的後端框架。</p>
<p>與 Huli 的順序不同,我們先從 <a href="https://laravel.com/docs/7.x/session">官方文件</a> 來看看 Laravel 怎麼處理 session:</p>
<p><img src="https://i.imgur.com/t1W97Kz.png" alt="Laravel 文件中的 HTTP Session 章節"><br>
<em>Laravel 文件中的 HTTP Session 章節</em></p>
<h2 id="laravel-怎麼生成-session-id-的">Laravel 怎麼生成 Session ID 的</h2>
<p>我們的構想是這樣:在文件裡面找找看有沒有跟生成 session ID 有關的 method,然後去追它的程式碼。</p>
<h3 id="「重新生成-session-id」">「重新生成 Session ID」</h3>
<p>查看文件的時候發現,Laravel 為了預防「固定 session ID (Session Fixation)」攻擊,提供了以下程式碼來重新生成 session ID,下面這段是 <a href="https://laravel.com/docs/6.x/session#regenerating-the-session-id">文件裡的範例</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$request</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">session</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">regenerate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>我們可以將 <code>regenerate()</code> 這個 function 作為我們追蹤原始碼的起點,看看它是怎麼生成 ID 的。這個 function 存在於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L481-L492">framework/src/Illuminate/Session/Store.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Generate a new session identifier.
*
* @param bool $destroy
* @return bool
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">regenerate</span><span class="token punctuation">(</span><span class="token variable">$destroy</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token function">tap</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">migrate</span><span class="token punctuation">(</span><span class="token variable">$destroy</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">regenerateToken</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>這裡我們看到了 <a href="https://laravel.com/docs/7.x/helpers#method-tap"><code>tap()</code></a> 這個 Laravel 的 Helper,功能是將第一個參數 <code>$value</code> 代入第二個參數 <code>$callback</code> function 中,並回傳結果。程式碼位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Support/helpers.php#L419-L437">framework/src/Illuminate/Support/helpers.php</a>,簡化後大致是像這樣:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">function</span> <span class="token function">tap</span><span class="token punctuation">(</span><span class="token variable">$value</span><span class="token punctuation">,</span> <span class="token variable">$callback</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$callback</span><span class="token punctuation">(</span><span class="token variable">$value</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token variable">$value</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>所以我們可以將 <code>regenerate()</code> 改寫成下面這樣:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Generate a new session identifier.
*
* @param bool $destroy
* @return bool
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">regenerate</span><span class="token punctuation">(</span><span class="token variable">$destroy</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$value</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">migrate</span><span class="token punctuation">(</span><span class="token variable">$destroy</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">regenerateToken</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token variable">$value</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<h3 id="建立-session-id">建立 Session ID</h3>
<p>接著我們追過去,到同檔案中的 <code>migrate()</code> 看看長什麼樣。PHPDoc 寫明了就是要建立 session ID:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L494-L511">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Generate a new session ID for the session.
*
* @param bool $destroy
* @return bool
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">migrate</span><span class="token punctuation">(</span><span class="token variable">$destroy</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$destroy</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">handler</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">destroy</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">setExists</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">setId</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">generateSessionId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Laravel 的函式命名的很易懂,我們在這裡找到了令人感興趣的 <code>generateSessionId()</code>,我們追下去:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L576-L584">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Get a new, random session ID.
*
* @return string
*/</span>
<span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function">generateSessionId</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> Str<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token number">40</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>哎呀,常寫 Laravel 的人一定很熟悉,這是我們常用的 <a href="https://laravel.com/docs/7.x/helpers#method-str-random">String helper 系列的東西</a>。不過不熟悉 helper 的人應該也可以猜測得到,這裡可能是要產生隨機 40 個字元的亂數。</p>
<p>到這裡,我們幾乎可以確定 Laravel 並不是使用 PHP 原生的 session 機制。</p>
<h3 id="laravel-並不是使用-php-原生的-session-機制">Laravel 並不是使用 PHP 原生的 session 機制</h3>
<p>我們到 random helper 確認看看,位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Support/Str.php#L413-L432">framework/src/Illuminate/Support/Str.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Generate a more truly "random" alpha-numeric string.
*
* @param int $length
* @return string
*/</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">random</span><span class="token punctuation">(</span><span class="token variable">$length</span> <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$string</span> <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token variable">$len</span> <span class="token operator">=</span> <span class="token function">strlen</span><span class="token punctuation">(</span><span class="token variable">$string</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator"><</span> <span class="token variable">$length</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$size</span> <span class="token operator">=</span> <span class="token variable">$length</span> <span class="token operator">-</span> <span class="token variable">$len</span><span class="token punctuation">;</span>
<span class="token variable">$bytes</span> <span class="token operator">=</span> <span class="token function">random_bytes</span><span class="token punctuation">(</span><span class="token variable">$size</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$string</span> <span class="token punctuation">.</span><span class="token operator">=</span> <span class="token function">substr</span><span class="token punctuation">(</span><span class="token function">str_replace</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'/'</span><span class="token punctuation">,</span> <span class="token string">'+'</span><span class="token punctuation">,</span> <span class="token string">'='</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token function">base64_encode</span><span class="token punctuation">(</span><span class="token variable">$bytes</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token variable">$size</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token variable">$string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>這個 helper 會先使用 PHP 7 新加入的 <a href="https://www.php.net/manual/en/function.random-bytes.php"><code>random_bytes</code></a> 原生 function 來生成指定長度的 <strong>安全隨機字串</strong>,接著依序執行以下事情:</p>
<ol>
<li><code>base64_encode</code>:將此隨機字串編碼成 base64。</li>
<li><code>str_replace</code>:將上述「編碼後的字串」移除 <code>'/'</code>、<code>'+'</code>、<code>'='</code> 這三種字元。</li>
<li><code>substr</code>:將上述「移除指定字元後的字串」的長度截短至指定長度(這裡是 40)</li>
<li><code>while</code>:如果長度不夠,就再用一樣的方式生成隨機字串,接在後面,直到長度剛好為止。</li>
</ol>
<p>至於 <code>random_bytes</code> 是怎麼生成安全隨機字串?PHP 官方文件提到,它其實是使用以下系統 function 的生成器來實作:</p>
<ul>
<li>Windows:<a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa379942(v=vs.85).aspx"><code>CryptGenRandom()</code></a></li>
<li>Linux:<a href="http://man7.org/linux/man-pages/man2/getrandom.2.html"><code>getrandom(2)</code></a> syscall</li>
<li>其他平台:<code>/dev/urandom</code></li>
</ul>
<p>這些產生器的正式名稱叫做 <a href="https://zh.wikipedia.org/wiki/%E5%AF%86%E7%A0%81%E5%AD%A6%E5%AE%89%E5%85%A8%E4%BC%AA%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%94%9F%E6%88%90%E5%99%A8">密碼學安全偽亂數生成器(CSPRNG)</a>。</p>
<h2 id="將-session-information-寫進-file">將 Session Information 寫進 File</h2>
<p>目前為止,我們已經知道 session ID 是怎麼生成的了。那麼是怎麼存 session information 呢?</p>
<h3 id="從-laravel-儲存-session-的指令下手">從 Laravel 儲存 Session 的指令下手</h3>
<p>先看 Laravel 儲存資料到 session 的兩個 <a href="https://laravel.com/docs/7.x/session#storing-data">寫法</a>,我們挑第一種寫法來研究 <sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$request</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">session</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">'key'</span><span class="token punctuation">,</span> <span class="token string">'value'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>我們看看 <code>put</code> 這個 method 在做什麼,位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L269-L285">framework/src/Illuminate/Session/Store.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Put a key / value pair or array of key / value pairs in the session.
*
* @param string|array $key
* @param mixed $value
* @return void
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">put</span><span class="token punctuation">(</span><span class="token variable">$key</span><span class="token punctuation">,</span> <span class="token variable">$value</span> <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span> <span class="token function">is_array</span><span class="token punctuation">(</span><span class="token variable">$key</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$key</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token variable">$key</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$value</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$key</span> <span class="token keyword">as</span> <span class="token variable">$arrayKey</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$arrayValue</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
Arr<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">attributes</span><span class="token punctuation">,</span> <span class="token variable">$arrayKey</span><span class="token punctuation">,</span> <span class="token variable">$arrayValue</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>這裡用到了 Laravel 另一個常見的 Array helper:<a href="https://laravel.com/docs/7.x/helpers#method-array-set">Arr::set</a>,功能是指定 key-value 到陣列中。這裡看起來 Laravel 是把我們給定的資料直接存到 <code>$this->attributes</code> 的陣列裡。</p>
<p>然後呢?程式碼怎麼沒了?</p>
<p>Laravel 的原始碼出了名的難追,沒關係,我們換個思路。</p>
<p>如果儲存資料的 function 只是將資料放進陣列,那總有一個地方在讀取那個陣列,並且把它寫入到 session 中吧。</p>
<h3 id="driver:儲存-session-的管道">Driver:儲存 Session 的管道</h3>
<p>從 session 文件的 <a href="https://laravel.com/docs/7.x/session#configuration">Configuration</a> 段落中可以得知,Laravel 預設支援以下的方式來儲存 session:</p>
<ul>
<li><code>file</code>:在伺服器中寫入一個檔案來儲存</li>
<li><code>cookie</code>:在瀏覽器的 cookie 中儲存加密後的資料(即 Cookie-based session)</li>
<li><code>database</code>:儲存到伺服器的關聯式資料庫中</li>
<li><code>memcached</code> / <code>redis</code>:儲存到伺服器的快取資料庫</li>
<li><code>array</code>:暫時儲存在伺服器 PHP 執行階段的一個陣列中(測試時使用)</li>
</ul>
<p>也許我們可以從這些 driver 找找看它寫入的機制。但是要去哪裡找 driver 的原始碼呢?</p>
<p>在 session 文件中的 <a href="https://laravel.com/docs/7.x/session#adding-custom-session-drivers">Adding Custom Session Drivers</a> 指出,我們還可以建立自訂的 driver 來支援 session,而這個 driver 的條件是:</p>
<ul>
<li>必須實作 <a href="https://www.php.net/manual/en/class.sessionhandlerinterface.php"><code>SessionHandlerInterface</code></a></li>
<li>註冊到 <a href="https://laravel.com/docs/7.x/providers">Service Provider</a></li>
</ul>
<p>看起來 service provider 會是進入 driver 的起點。那我們找找看有沒有 session 的 service provider?</p>
<h3 id="session-的-service-provider">Session 的 Service Provider</h3>
<p>當然有。使用者建立 Laravel 專案時,預設就會把 <code>SessionServiceProvider</code> 自動載入了,設定檔在專案的 <a href="https://github.com/laravel/laravel/blob/v7.6.0/config/app.php#L160">config/app.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/</span>
<span class="token string">'providers'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token punctuation">[</span>
<span class="token comment">// 略...</span>
Illuminate\<span class="token package">Session<span class="token punctuation">\</span>SessionServiceProvider</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token keyword">class</span><span class="token punctuation">,</span>
<span class="token comment">// 略...</span>
<span class="token punctuation">]</span><span class="token punctuation">,</span>
</code></pre>
<p>繼續追下去,我們來看看 <code>SessionServiceProvider</code> 做了什麼,原始碼在<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/SessionServiceProvider.php#L10-L22">framework/src/Illuminate/Session/SessionServiceProvider.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Register the service provider.
*
* @return void
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">register</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">registerSessionManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">registerSessionDriver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">app</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">singleton</span><span class="token punctuation">(</span>StartSession<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>前兩行看起來是把 session 的 Manager 和 driver 註冊起來。令人比較感興趣的是第三行,使用 singleton 模式 <a href="https://laravel.com/docs/7.x/container#binding-basics">綁定</a> <code>StartSession::class</code> 到 Laravel 的 <a href="https://laravel.com/docs/7.x/container">服務容器(Service Container)</a> 中。</p>
<p>我們現在的目標是希望知道 Laravel 如何寫入 session,這裡的 <code>StartSession</code> 看起來像是 session 的操作邏輯,我們進去看看。</p>
<h3 id="startsession-是個-middleware"><code>StartSession</code> 是個 Middleware</h3>
<p><code>StartSession</code> 位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Middleware/StartSession.php#L34-L68">framework/src/Illuminate/Session/Middleware/StartSession.php</a>。</p>
<p>從 Namespace 可以看出來,是個 <a href="https://laravel.com/docs/7.x/middleware">Middleware</a>!<s>果然英雄所見略同,</s> Laravel 和 Express、Rails 一樣都是使用 middleware 來實作 session 機制。</p>
<p>我們把專注力放在 <code>handle</code> method:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">handle</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token punctuation">,</span> Closure <span class="token variable">$next</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">sessionConfigured</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token variable">$next</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// If a session driver has been configured, we will need to start the session here</span>
<span class="token comment">// so that the data is ready for an application. Note that the Laravel sessions</span>
<span class="token comment">// do not make use of PHP "native" sessions in any way since they are crappy.</span>
<span class="token variable">$request</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">setLaravelSession</span><span class="token punctuation">(</span>
<span class="token variable">$session</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">startSession</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">collectGarbage</span><span class="token punctuation">(</span><span class="token variable">$session</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$response</span> <span class="token operator">=</span> <span class="token variable">$next</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">storeCurrentUrl</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token punctuation">,</span> <span class="token variable">$session</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">addCookieToResponse</span><span class="token punctuation">(</span><span class="token variable">$response</span><span class="token punctuation">,</span> <span class="token variable">$session</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Again, if the session has been configured we will need to close out the session</span>
<span class="token comment">// so that the attributes may be persisted to some storage medium. We will also</span>
<span class="token comment">// add the session identifier cookie to the application response headers now.</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">saveSession</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token variable">$response</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>這裡的註解強調了 <strong>Laravel 並沒有在任何一個地方使用 PHP 原生的 session 機制</strong>,因為它 crappy 😅,這個我們後面來談談。</p>
<p>整理一下這個 Middleware 做了什麼:</p>
<ol>
<li>檢查 session 設定,沒有設定的話就直接離開 middleware</li>
<li>啟動 session,將 session data 設定到 request 中</li>
<li>清除過期的 session 垃圾(依機率觸發)</li>
<li>通過 middleware,request 進入應用程式,response 回到 middleware</li>
<li>符合指定條件時儲存當前 request 的 URL 到 session</li>
<li>將 session cookie 加到 response 中</li>
<li>儲存 session 資料</li>
<li>離開 middleware</li>
</ol>
<h3 id="savesession:儲存-session-資料"><code>saveSession()</code>:儲存 Session 資料</h3>
<p>為了找尋如何儲存 session 資料,我們進入第七點,也就是 <code>saveSession()</code> 這個 method:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Middleware/StartSession.php#L162-L171">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Save the session data to storage.
*
* @param \Illuminate\Http\Request $request
* @return void
*/</span>
<span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function">saveSession</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">manager</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">driver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>我們將依序處理這個漫長的過程(偷偷爆雷):</p>
<ol>
<li>找到 <code>manager</code>,也就是 SessionManager</li>
<li>執行 <code>driver()</code> 取得 driver instance</li>
<li>再到 <code>save()</code> 執行完整個 session 的儲存動作</li>
</ol>
<h3 id="sessionmanager">SessionManager</h3>
<p>依照慣例,<code>$this->manager</code> 是從建構子(constructor)裡 <a href="https://laravel.com/docs/7.x/container">依賴注入(Dependency injection, DI)</a> 進來的,是個 <code>SessionManager</code>:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Middleware/StartSession.php#L23-L32">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Create a new session middleware.
*
* @param \Illuminate\Session\SessionManager $manager
* @return void
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">__construct</span><span class="token punctuation">(</span>SessionManager <span class="token variable">$manager</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">manager</span> <span class="token operator">=</span> <span class="token variable">$manager</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>我們找到了 <code>SessionManager</code>,位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/SessionManager.php">framework/src/Illuminate/Session/SessionManager.php</a>。</p>
<h3 id="managerdriver:取得-driver-instance"><code>Manager::driver()</code>:取得 Driver Instance</h3>
<p>但是,<code>SessionManager</code> 裡面並沒有 <code>driver()</code> 的蹤跡。</p>
<p><code>SessionManager</code> 本身是繼承 Manager:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/SessionManager.php#L7">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">class</span> <span class="token class-name">SessionManager</span> <span class="token keyword">extends</span> <span class="token class-name">Manager</span>
</code></pre>
<p>剛剛 <code>saveSession()</code> 中提到的 <code>driver()</code> 就位於 Manager 中。為了方便閱讀,這裡我們稍微簡化一下內容:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Support/Manager.php#L68-L94">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Get a driver instance.
*
* @param string|null $driver
* @return mixed
*
* @throws \InvalidArgumentException
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">driver</span><span class="token punctuation">(</span><span class="token variable">$driver</span> <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$driver</span> <span class="token operator">=</span> <span class="token variable">$driver</span> <span class="token operator">?</span><span class="token punctuation">:</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getDefaultDriver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// If the given driver has not been created before, we will create the instances</span>
<span class="token comment">// here and cache it so we can return it next time very quickly. If there is</span>
<span class="token comment">// already a driver created by this name, we'll just return that instance.</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span> <span class="token function">isset</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">drivers</span><span class="token punctuation">[</span><span class="token variable">$driver</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">drivers</span><span class="token punctuation">[</span><span class="token variable">$driver</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">createDriver</span><span class="token punctuation">(</span><span class="token variable">$driver</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">drivers</span><span class="token punctuation">[</span><span class="token variable">$driver</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>先看第一段 code。</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$driver</span> <span class="token operator">=</span> <span class="token variable">$driver</span> <span class="token operator">?</span><span class="token punctuation">:</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getDefaultDriver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>還記得 <code>saveSession()</code> 中的那行 code 嗎?<strong>(第一次問你,後面會再問你一次)</strong></p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">manager</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">driver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>由於我們在呼叫 <code>driver()</code> 時並沒有指定參數,因此會執行 <code>?:</code> 後方的 <code>getDefaultDriver()</code> ,然後把它的回傳值賦值給 <code>$driver</code>。</p>
<p>從 <code>getDefaultDriver()</code> 的名稱看起來,這裡會去抓我們在專案 config 中指定的預設 driver。我們暫時回去 <code>SessionManager</code> 看 <code>getDefaultDriver()</code> 確認一下:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/SessionManager.php#L215-L223">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Get the default session driver name.
*
* @return string
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">getDefaultDriver</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">config</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'session.driver'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>果然沒錯。Laravel 會依照我們在專案的 config/session.php 中的設定來選擇要使用的 Driver,<a href="https://github.com/laravel/laravel/blob/v7.6.0/config/session.php#L7-L21">預設值</a> 是 <code>File</code>。</p>
<p>回來看 <code>driver()</code> 第二段 code。</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">// If the given driver has not been created before, we will create the instances</span>
<span class="token comment">// here and cache it so we can return it next time very quickly. If there is</span>
<span class="token comment">// already a driver created by this name, we'll just return that instance.</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span> <span class="token function">isset</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">drivers</span><span class="token punctuation">[</span><span class="token variable">$driver</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">drivers</span><span class="token punctuation">[</span><span class="token variable">$driver</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">createDriver</span><span class="token punctuation">(</span><span class="token variable">$driver</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>這裡的註解很清楚,Laravel 會依據指定的 driver 執行 <code>createDriver()</code> 來建立一個 instance,順便把這個 instance cache 起來。</p>
<p>我們來看 <code>createDriver()</code> 怎麼作業的,為了方便閱讀我們簡化了一下內容,原始內容請見 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Support/Manager.php#L96-L120">原始碼</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Create a new driver instance.
*
* @param string $driver
* @return mixed
*
* @throws \InvalidArgumentException
*/</span>
<span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function">createDriver</span><span class="token punctuation">(</span><span class="token variable">$driver</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$method</span> <span class="token operator">=</span> <span class="token string">'create'</span><span class="token punctuation">.</span>Str<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">studly</span><span class="token punctuation">(</span><span class="token variable">$driver</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token string">'Driver'</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">method_exists</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token punctuation">,</span> <span class="token variable">$method</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token variable">$method</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>裡面用到了 <a href="https://laravel.com/docs/7.x/helpers#method-studly-case">Str::studly</a> 這個 helper,會把字串轉成類似大駝峰式命名法的 <a href="https://zh.wikipedia.org/wiki/%E9%A7%9D%E5%B3%B0%E5%BC%8F%E5%A4%A7%E5%B0%8F%E5%AF%AB#%E5%85%B6%E4%BB%96%E7%9B%B8%E9%97%9C%E6%A0%BC%E5%BC%8F">Studly caps</a>。所以我們要找的是 <code>createFileDriver()</code>、<code>createCookieDriver()</code> 等等這種命名規則的 method。</p>
<h3 id="createfiledriver:建立-file-session-driver-的-instance"><code>createFileDriver()</code>:建立 File Session Driver 的 Instance</h3>
<p>我們以預設的 File driver 為例,<code>createFileDriver()</code> 就在 <code>SessionManager</code> 中,長這樣:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/SessionManager.php#L54-L76">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Create an instance of the file session driver.
*
* @return \Illuminate\Session\Store
*/</span>
<span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function">createFileDriver</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">createNativeDriver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* Create an instance of the file session driver.
*
* @return \Illuminate\Session\Store
*/</span>
<span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function">createNativeDriver</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$lifetime</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">config</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'session.lifetime'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">buildSession</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FileSessionHandler</span><span class="token punctuation">(</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">container</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">make</span><span class="token punctuation">(</span><span class="token string">'files'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">config</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'session.files'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token variable">$lifetime</span>
<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p><code>createFileDriver()</code> 直接導向 <code>createNativeDriver()</code>。</p>
<p><code>createNativeDriver()</code> 中的第一行,<code>$lifetime</code> 賦值為我們在專案的 config 中指定 session lifetime 的時間(<a href="https://github.com/laravel/laravel/blob/v7.6.0/config/session.php#L23-L34">預設值</a> 為 120 分鐘):</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$lifetime</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">config</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'session.lifetime'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>接著看第二行,看起來有點小複雜?我們先看小括號裡面,排版一下:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">new</span> <span class="token class-name">FileSessionHandler</span><span class="token punctuation">(</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">container</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">make</span><span class="token punctuation">(</span><span class="token string">'files'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">config</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'session.files'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token variable">$lifetime</span>
<span class="token punctuation">)</span>
</code></pre>
<p>如果你瀏覽一下其餘 driver 所對應的 <code>create____Driver()</code> 系列 method,可以發現都是類似的流程,不外乎建立一個該 driver 的 SessionHandler object。像 File driver 建立的就是 <code>FileSessionHandler</code>,在這裡傳入的依序是:</p>
<ul>
<li>File <a href="https://laravel.com/docs/7.x/facades">Facade</a>(後面說明)</li>
<li>session 檔案儲存的位置(<a href="https://github.com/laravel/laravel/blob/v7.6.0/config/session.php#L51-L62">預設值</a> 為 <code>'/storage/framework/sessions'</code>)</li>
<li>剛剛第一行拿到的 session lifetime</li>
</ul>
<p>稍微瞄一下 <code>FileSessionHandler</code> 的建構子,位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/FileSessionHandler.php#L33-L46">framework/src/Illuminate/Session/FileSessionHandler.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Create a new file driven handler instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @param string $path
* @param int $minutes
* @return void
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">__construct</span><span class="token punctuation">(</span>Filesystem <span class="token variable">$files</span><span class="token punctuation">,</span> <span class="token variable">$path</span><span class="token punctuation">,</span> <span class="token variable">$minutes</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">path</span> <span class="token operator">=</span> <span class="token variable">$path</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">files</span> <span class="token operator">=</span> <span class="token variable">$files</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">minutes</span> <span class="token operator">=</span> <span class="token variable">$minutes</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>建構子把三個傳來的參數存到 property,簡單整理就好,很後面才會用到:</p>
<ul>
<li><code>path</code>:存 session 檔案儲存的位置(預設為 <code>'/storage/framework/sessions'</code>)</li>
<li><code>files</code>:存 File Facade</li>
<li><code>minutes</code>:存 session lifetime(預設為 120 分鐘)</li>
</ul>
<p>接著回到 <code>createNativeDriver()</code>,將剛剛建好的 <code>FileSessionHandler</code> 傳入 <code>buildSession()</code> 執行,就開始建立 session 了。</p>
<h3 id="buildsession-建立-session-instance"><code>buildSession()</code>: 建立 Session Instance</h3>
<p>從 <code>buildSession()</code> 這名稱看起來,就有進入重頭戲的感覺。位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/SessionManager.php#L179-L190">framework/src/Illuminate/Session/SessionManager.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Build the session instance.
*
* @param \SessionHandlerInterface $handler
* @return \Illuminate\Session\Store
*/</span>
<span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function">buildSession</span><span class="token punctuation">(</span><span class="token variable">$handler</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">config</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'session.encrypt'</span><span class="token punctuation">)</span>
<span class="token operator">?</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">buildEncryptedSession</span><span class="token punctuation">(</span><span class="token variable">$handler</span><span class="token punctuation">)</span>
<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token class-name">Store</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">config</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'session.cookie'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token variable">$handler</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>首先會去專案的 <a href="https://github.com/laravel/laravel/blob/v7.6.0/config/session.php#L38-L49">config/session.php</a> 中抓取加密的設定值。當專案將其設定為 <code>true</code> 時,Laravel 會自動在儲存 session 前先將內容進行加密。</p>
<p>由於加密預設值為 <code>false</code>,我們可以看到 Laravel 會先取得專案的 config/session.php 中指定的 session cookie name(<a href="https://github.com/laravel/laravel/blob/v7.6.0/config/session.php#L116-L130">預設值</a> 為 <code>'專案名_session'</code>),然後將其與 <code>FileSessionHandler</code> 一起傳給 <code>Store</code> 的建構子。</p>
<h3 id="store:session-instance-本體"><code>Store</code>:Session Instance 本體</h3>
<p><code>Store</code>?有沒有很熟悉,就是我們第一個段落找到的 class。也就是說,其實 <code>$request->session()</code> 所回傳的實體,就是當前 request 的 session instance 本體。</p>
<p><code>Store</code> 位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L49-L62">framework/src/Illuminate/Session/Store.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Create a new session instance.
*
* @param string $name
* @param \SessionHandlerInterface $handler
* @param string|null $id
* @return void
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">__construct</span><span class="token punctuation">(</span><span class="token variable">$name</span><span class="token punctuation">,</span> SessionHandlerInterface <span class="token variable">$handler</span><span class="token punctuation">,</span> <span class="token variable">$id</span> <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">setId</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">name</span> <span class="token operator">=</span> <span class="token variable">$name</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">handler</span> <span class="token operator">=</span> <span class="token variable">$handler</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>字串<code>'專案名_session'</code>和 <code>FileSessionHandler</code> 都被各自賦值到 object 的 <code>name</code> 和 <code>handler</code> property 中。而由於剛才 <code>buildSession()</code> 並沒有指定 <code>$id</code>,我們將 <code>setId()</code> 帶入 <code>null</code>:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L554-L563">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Set the session ID.
*
* @param string $id
* @return void
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">setId</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">id</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">isValidId</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token variable">$id</span> <span class="token punctuation">:</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">generateSessionId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p><code>$this->id</code> 會先執行<code>isValidId()</code> 判斷 ID 是否有效:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L565-L574">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Determine if this is a valid session ID.
*
* @param string $id
* @return bool
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">isValidId</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token function">is_string</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token function">ctype_alnum</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token function">strlen</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token number">40</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>很明顯地,<code>null</code> 並不是一個字串,所以後續的判斷式也不會執行了,直接回傳 <code>false</code> 到剛剛的 <code>setId()</code>。<code>$this->id</code> 將被賦值為 <code>$this->generateSessionId()</code> 的結果,也就是產生新的 ID。</p>
<p>這個 <code>generateSessionId()</code> 已經在 <strong>〈Laravel 怎麼生成 Session ID?〉</strong> 段落追蹤過,因此我們知道 ID 為隨機 40 個字元。</p>
<h3 id="return-再-return:回到-savesession">return 再 return:回到 <code>saveSession()</code></h3>
<p>我是誰,我在哪?</p>
<p>還記得 <code>saveSession()</code> 中的那行 code 嗎?(這不就來問你第二次了嗎)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">manager</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">driver</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>我們翻山越嶺,總算從 <code>driver()</code> 那裡拿到了 session instance 本體,也就是 <code>Store</code> object。接著就要來儲存啦!</p>
<h3 id="save:-儲存-session-資料到指定儲存空間(file)"><code>save()</code>: 儲存 Session 資料到指定儲存空間(File)</h3>
<p>辛苦了!我們到了最後的環節。</p>
<p>我們從 session instance(也就是 <code>Store</code>)中可以找到 <code>save()</code> 這個 method,位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L119-L133">framework/src/Illuminate/Session/Store.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Save the session data to storage.
*
* @return void
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">ageFlashData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">handler</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">write</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">prepareForStorage</span><span class="token punctuation">(</span>
<span class="token function">serialize</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">attributes</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">started</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>第一段先執行 <code>ageFlashData()</code>,過去看看:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L146-L158">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Age the flash data for the session.
*
* @return void
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">ageFlashData</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">forget</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'_flash.old'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">'_flash.old'</span><span class="token punctuation">,</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'_flash.new'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">'_flash.new'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>這裡會做快閃資料(Flash Data)生命週期有關的事,由於這裡不是我們的重點,我們就不深入拆解裡面的 function 了,簡單敘述帶過:</p>
<ol>
<li>將舊的快閃資料列表找出來,從 session 裡清掉</li>
<li>把舊的資料列表取代成新的資料列表</li>
<li>把新的資料列表清空</li>
</ol>
<p>其實這也就是 <a href="https://laravel.com/docs/7.x/session#flash-data">session 文件</a> 裡提到的快閃資料機制。</p>
<p>回到上一層 <code>save()</code>,接著看第二段,執行 <code>$this->handler->write()</code>,我們重新排版一下:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">handler</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">write</span><span class="token punctuation">(</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">prepareForStorage</span><span class="token punctuation">(</span>
<span class="token function">serialize</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">attributes</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>首先回想一下,還記得 <code>buildSession()</code> 的段落嗎?我們有將 <code>FileSessionHandler</code> 傳給建構子,所以這裡的 <code>$this->handler</code> 就是 <code>FileSessionHandler</code>。</p>
<p>先看它傳什麼進去,簡單整理:(點選 function 看原始碼位置)</p>
<ul>
<li><a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L544-L552"><code>getId()</code></a>:這個 function 會取得建構 <code>Store</code> 時傳入的 session ID</li>
<li><a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L135-L144"><code>prepareForStorage()</code></a>:這個 function 會直接回傳括號裡的資料 <sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup>
<ul>
<li><code>serialize($this->attributes)</code>:使用 PHP 原生 <a href="https://www.php.net/manual/en/function.serialize.php"><code>serialize()</code></a> 來序列化 <code>$this->attributes</code></li>
</ul>
</li>
</ul>
<p>這裡很明顯地,要儲存的 session 資料就在 <code>$this->attributes</code> 裡。</p>
<p>還記得我們是什麼時候把資料放進去的嗎?請往上找到 <strong>〈從 Laravel 儲存 Session 的指令下手〉</strong> 段落。</p>
<h3 id="write:寫入-session-資料到-file"><code>write()</code>:寫入 Session 資料到 File</h3>
<p>準備好所有傳入參數後,接著就是呼叫 <code>FileSessionHandler::write()</code>,它是位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/FileSessionHandler.php#L78-L86">framework/src/Illuminate/Session/FileSessionHandler.php</a></p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* {@inheritdoc}
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">write</span><span class="token punctuation">(</span><span class="token variable">$sessionId</span><span class="token punctuation">,</span> <span class="token variable">$data</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">files</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">put</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">path</span><span class="token punctuation">.</span><span class="token string">'/'</span><span class="token punctuation">.</span><span class="token variable">$sessionId</span><span class="token punctuation">,</span> <span class="token variable">$data</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>這裡的 PHPDoc 是直接繼承自 PHP 原生的 <code>SessionHandlerInterface::write()</code>。在這裡我們可以發現,雖然 Laravel 自己實作 session 機制,但還是有實作 PHP 原生的 interface(但也只有這個環節)。</p>
<p>我們來看 <code>SessionHandlerInterface::write()</code> 的 PHPDoc 寫了什麼:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Write session data
* @link https://php.net/manual/en/sessionhandlerinterface.write.php
* @param string $session_id The session id.
* @param string $session_data <p>
* The encoded session data. This data is the
* result of the PHP internally encoding
* the $_SESSION superglobal to a serialized
* string and passing it as this parameter.
* Please note sessions use an alternative serialization method.
* </p>
* @return bool <p>
* The return value (usually TRUE on success, FALSE on failure).
* Note this value is returned internally to PHP for processing.
* </p>
* @since 5.4
*/</span>
</code></pre>
<p>簡單說就是把 session 的資料編碼(encoded)成序列化(serialized)字串,然後儲存。詳細資訊可以在 <a href="https://www.php.net/manual/en/sessionhandlerinterface.write.php">PHP 官方 SessionHandlerInterface::write 文件</a> 中找到。</p>
<p>好,我們回到 <code>FileSessionHandler::write()</code>,重點只有一行:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">files</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">put</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">path</span><span class="token punctuation">.</span><span class="token string">'/'</span><span class="token punctuation">.</span><span class="token variable">$sessionId</span><span class="token punctuation">,</span> <span class="token variable">$data</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>這裡的 $this->files 在 <code>Store</code> 建構時存了 Laravel 提供的 File Facade。</p>
<h3 id="分岔一下,來講-file-facade">分岔一下,來講 File Facade</h3>
<p><a href="https://laravel.com/docs/7.x/facades">Facade</a> 是什麼呢?它是一個靜態代理(static proxy),將 Laravel 的幾乎所有功能都用 <a href="https://laravel.com/docs/7.x/container">服務容器(Service Container)</a>綁定成靜態 Class。這其實也是 <a href="https://zh.wikipedia.org/zh-tw/%E5%A4%96%E8%A7%80%E6%A8%A1%E5%BC%8F">一種 design pattern</a>。</p>
<p>簡單講,有了 Facade,可以很方便的使用分散在各個 namespace 的 Laravel 功能(或說是 class)。</p>
<p>所以 File Facade 對應到原始 class 是什麼呢?<a href="https://laravel.com/docs/7.x/facades#facade-class-reference">Facade 文件</a> 可以看到,是 <code>Illuminate\Filesystem\Filesystem</code>。其實在 <code>FileSessionHandler</code> 的建構子也寫了,回顧一下:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/FileSessionHandler.php#L41">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">__construct</span><span class="token punctuation">(</span>Filesystem <span class="token variable">$files</span><span class="token punctuation">,</span> <span class="token variable">$path</span><span class="token punctuation">,</span> <span class="token variable">$minutes</span><span class="token punctuation">)</span>
</code></pre>
<h3 id="回來-write:繼續寫入-session-資料到-file">回來 <code>write()</code>:繼續寫入 Session 資料到 File</h3>
<p>所以我們回到 <code>write()</code> 的那一行:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">files</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">put</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">path</span><span class="token punctuation">.</span><span class="token string">'/'</span><span class="token punctuation">.</span><span class="token variable">$sessionId</span><span class="token punctuation">,</span> <span class="token variable">$data</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>先看傳入參數:</p>
<ul>
<li>第一個參數:存 session 檔案儲存的位置,後面加上 session ID</li>
<li>第二個參數:序列化後的 session data</li>
<li>第三個參數:<code>true</code></li>
</ul>
<h3 id="put:把內容寫入檔案"><code>put()</code>:把內容寫入檔案</h3>
<p>接著找到 <code>Filesystem::put()</code>,位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Filesystem/Filesystem.php#L123-L134">framework/src/Illuminate/Filesystem/Filesystem.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Write the contents of a file.
*
* @param string $path
* @param string $contents
* @param bool $lock
* @return int|bool
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">put</span><span class="token punctuation">(</span><span class="token variable">$path</span><span class="token punctuation">,</span> <span class="token variable">$contents</span><span class="token punctuation">,</span> <span class="token variable">$lock</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token function">file_put_contents</span><span class="token punctuation">(</span><span class="token variable">$path</span><span class="token punctuation">,</span> <span class="token variable">$contents</span><span class="token punctuation">,</span> <span class="token variable">$lock</span> <span class="token operator">?</span> <span class="token constant">LOCK_EX</span> <span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>看到這個 PHPDoc 鬆了一口氣,終於!我們要把資料寫到檔案了!</p>
<p>這裡使用 PHP 原生的 <a href="https://www.php.net/manual/en/function.file-put-contents.php">file_put_contents</a> 來寫檔,參數對應如下:</p>
<ul>
<li>第一個參數 <code>$filename</code>:檔案位置字串,傳入我們的 <code>$path</code>。
<ul>
<li>變數內容為 session 檔案儲存的位置,後面加上 session ID。</li>
<li>預設資料:<code>'/storage/framework/sessions/${SessionId}'</code></li>
</ul>
</li>
<li>第二個參數 <code>$data</code>:寫入檔案的資料,傳入我們的 <code>$contents</code>。
<ul>
<li>變數內容為序列化後的 session data。</li>
</ul>
</li>
<li>第三個參數 <code>$flags</code>:寫入檔案時的設定,這裡設定成 <code>LOCK_EX</code>。
<ul>
<li>這裡我們指定檔案在寫入時會鎖定,防止其他人寫入。</li>
</ul>
</li>
</ul>
<p>這樣就完成 session 的寫檔了!</p>
<h2 id="延伸討論:說好的-cookie-based-session-呢">延伸討論:說好的 cookie-based session 呢</h2>
<p>欸不是,應該要討論 cookie 吧,怎麼沒看到?因為我一直照 Laravel 預設值去追,結果寫完才發現我寫成 File driver 了 🤦♂️</p>
<p>(到這裡才發現,Huli 在 PHP 段落也是寫 file 版啊)</p>
<p>那麼,是怎麼存 session information 到 cookie 的呢?我們從頭開始,快轉到 driver 的部分吧。</p>
<h3 id="createcookiedriver:建立-cookie-session-driver-的-instance"><code>createCookieDriver()</code>:建立 Cookie Session Driver 的 Instance</h3>
<p>當專案將 session 的 driver 指定為 <code>'cookie'</code> 時,上方 <code>createFileDriver()</code> 部分就會被改成呼叫 <code>createCookieDriver()</code>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Create an instance of the "cookie" session driver.
*
* @return \Illuminate\Session\Store
*/</span>
<span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function">createCookieDriver</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">buildSession</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">CookieSessionHandler</span><span class="token punctuation">(</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">container</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">make</span><span class="token punctuation">(</span><span class="token string">'cookie'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">config</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'session.lifetime'</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>在 cookie driver 建立的就是 <code>CookieSessionHandler</code>,在這裡傳入的依序是:</p>
<ul>
<li>Cookie Facade</li>
<li>session lifetime</li>
</ul>
<p>長得跟 <code>createFileDriver()</code> 流程幾乎一模一樣,差異是:</p>
<ul>
<li><code>FileSessionHandler</code> 換成了 <code>CookieSessionHandler</code></li>
<li><code>CookieSessionHandler</code> 少了中間的參數,也就是指定寫入位置的部分</li>
<li>Facade 也從 <code>'file'</code>(<code>Illuminate\Filesystem\Filesystem</code>) 換成了 <code>'cookie'</code>(<code>Illuminate\Cookie\CookieJar</code>)</li>
</ul>
<p>這樣會有什麼變化呢?我們追追看… 🤷♂️</p>
<h3 id="驚喜地發現,中間流程都一樣!">驚喜地發現,中間流程都一樣!</h3>
<p>Laravel 的架構做得如此優美,做到了 <a href="https://zh.wikipedia.org/wiki/%E4%B8%80%E6%AC%A1%E4%B8%94%E4%BB%85%E4%B8%80%E6%AC%A1">DRY 原則(Don’t repeat yourself)</a>。</p>
<p>由於中間 handler 參數都是使用了 interface 來規範,要一直到 handler 開始處理後流程才會分岔,我們直接跳到 <code>save()</code>。</p>
<h3 id="save:-儲存-session-資料到指定儲存空間(cookie)"><code>save()</code>: 儲存 Session 資料到指定儲存空間(Cookie)</h3>
<p>辛苦了!我們 <strong>又</strong> 到了最後的環節。</p>
<p>我們直接快轉到 <code>$this->handler->write()</code>,這次我們的 <code>$this->handler</code> 換成了 <code>CookieSessionHandler</code>。</p>
<h3 id="write:寫入-session-資料到-cookie"><code>write()</code>:寫入 Session 資料到 Cookie</h3>
<p>準備好所有傳入參數後,接著就是呼叫 <code>CookieSessionHandler::write()</code>,它是位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/CookieSessionHandler.php#L80-L91">framework/src/Illuminate/Session/CookieSessionHandler.php</a></p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* {@inheritdoc}
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">write</span><span class="token punctuation">(</span><span class="token variable">$sessionId</span><span class="token punctuation">,</span> <span class="token variable">$data</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">cookie</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">queue</span><span class="token punctuation">(</span><span class="token variable">$sessionId</span><span class="token punctuation">,</span> <span class="token function">json_encode</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
<span class="token string">'data'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$data</span><span class="token punctuation">,</span>
<span class="token string">'expires'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">availableAt</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">minutes</span> <span class="token operator">*</span> <span class="token number">60</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">minutes</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>首先不意外的,<code>CookieSessionHandler</code> 依然是實作 PHP 原生的 <code>SessionHandlerInterface</code>。</p>
<p>然後,這裡開始有點不一樣了。</p>
<p>這裡的 <code>$this->cookie</code> 在 <code>Store</code> 建構時存了 Laravel 提供的 Cookie Facade,剛說過了,本尊是 <code>Illuminate\Cookie\CookieJar</code>。</p>
<p>所以我們回到 <code>write()</code> 接續看,稍微換個排版:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">cookie</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">queue</span><span class="token punctuation">(</span>
<span class="token variable">$sessionId</span><span class="token punctuation">,</span>
<span class="token function">json_encode</span><span class="token punctuation">(</span>
<span class="token punctuation">[</span>
<span class="token string">'data'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$data</span><span class="token punctuation">,</span>
<span class="token string">'expires'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">availableAt</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">minutes</span> <span class="token operator">*</span> <span class="token number">60</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span>
<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">minutes</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>這裡參數中有個沒看過的 <code>availableAt()</code>,我們先解決它。它來自 <code>InteractsWithTime</code> 這個 Trait,我們傳入的參數預設為 <code>120 * 60</code>。</p>
<h3 id="availableat:取得-session-到期時間的-unix-timestamp"><code>availableAt()</code>:取得 Session 到期時間的 UNIX Timestamp</h3>
<p><code>availableAt()</code> 位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Support/InteractsWithTime.php#L25-L53">framework/src/Illuminate/Support/InteractsWithTime.php</a></p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Get the "available at" UNIX timestamp.
*
* @param \DateTimeInterface|\DateInterval|int $delay
* @return int
*/</span>
<span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function">availableAt</span><span class="token punctuation">(</span><span class="token variable">$delay</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$delay</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">parseDateInterval</span><span class="token punctuation">(</span><span class="token variable">$delay</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token variable">$delay</span> <span class="token keyword">instanceof</span> <span class="token class-name">DateTimeInterface</span>
<span class="token operator">?</span> <span class="token variable">$delay</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getTimestamp</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">:</span> Carbon<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">addRealSeconds</span><span class="token punctuation">(</span><span class="token variable">$delay</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getTimestamp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* If the given value is an interval, convert it to a DateTime instance.
*
* @param \DateTimeInterface|\DateInterval|int $delay
* @return \DateTimeInterface|int
*/</span>
<span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function">parseDateInterval</span><span class="token punctuation">(</span><span class="token variable">$delay</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$delay</span> <span class="token keyword">instanceof</span> <span class="token class-name">DateInterval</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$delay</span> <span class="token operator">=</span> Carbon<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">add</span><span class="token punctuation">(</span><span class="token variable">$delay</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token variable">$delay</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>首先第一段,<code>$delay</code> 如果是 <code>DateInterval</code> 型態的話,會被轉為 <a href="https://carbon.nesbot.com/"><code>Carbon</code></a> object(PHP 界的 Moment.js)。我們這裡 <code>$delay</code> 是 <code>int</code> 型態,所以不會被轉換。</p>
<p>第二段,如果是 <code>DateTimeInterface</code>(<code>Carbon</code> object 實作的 Interface)的話就轉為 Timestamp;否則,建立現在時間的 <code>Carbon</code> object,加上 <code>$delay</code> 後再轉為 Timestamp。我們明顯是後者的情況。</p>
<h3 id="回來-write:繼續寫入-session-資料到-cookie">回來 <code>write()</code>:繼續寫入 Session 資料到 Cookie</h3>
<p>所以我們回到 <code>write()</code>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">cookie</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">queue</span><span class="token punctuation">(</span>
<span class="token variable">$sessionId</span><span class="token punctuation">,</span>
<span class="token function">json_encode</span><span class="token punctuation">(</span>
<span class="token punctuation">[</span>
<span class="token string">'data'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$data</span><span class="token punctuation">,</span>
<span class="token string">'expires'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">availableAt</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">minutes</span> <span class="token operator">*</span> <span class="token number">60</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span>
<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">minutes</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>先看傳入參數:</p>
<ul>
<li>第一個參數:就是個 session ID</li>
<li>第二個參數:一個陣列,放入了 session data 以及過期時間的 timestamp,然後轉成 JSON</li>
<li>第三個參數:session lifetime(預設為 120 分鐘)</li>
</ul>
<h3 id="queue:把內容排入-cookie-的-queue"><code>queue()</code>:把內容排入 Cookie 的 Queue</h3>
<p>接著找到 <code>CookieJar::queue()</code>,位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Cookie/CookieJar.php#L135-L154">framework/src/Illuminate/Cookie/CookieJar.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Queue a cookie to send with the next response.
*
* @param array $parameters
* @return void
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">queue</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token variable">$parameters</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isset</span><span class="token punctuation">(</span><span class="token variable">$parameters</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token variable">$parameters</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token keyword">instanceof</span> <span class="token class-name">Cookie</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$cookie</span> <span class="token operator">=</span> <span class="token variable">$parameters</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token variable">$cookie</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token variable">$parameters</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span> <span class="token function">isset</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">queued</span><span class="token punctuation">[</span><span class="token variable">$cookie</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">queued</span><span class="token punctuation">[</span><span class="token variable">$cookie</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">queued</span><span class="token punctuation">[</span><span class="token variable">$cookie</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token variable">$cookie</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token variable">$cookie</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>這裡往裡面追會太繁瑣,所以直接用文字說明。</p>
<p><code>queue()</code> 是個 <a href="https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list">Variadic function</a>,將所有傳入的參數依序轉成 <code>$parameters</code> 陣列。</p>
<p>第一段的 if 判斷 <code>$parameters[0]</code> 是不是 <code>Cookie</code> object,這裡我們傳入的是 session ID 字串,所以進入 else 範圍,執行 <code>make()</code>。而 <code>make()</code> 會將我們傳入的參數組合成 Symfony <sup class="footnote-ref"><a href="#fn3" id="fnref3">3</a></sup> 的 <code>Cookie</code> object(<code>\Symfony\Component\HttpFoundation\Cookie</code>)回傳。</p>
<p>第二段的 if 檢查目前的 queue 有沒有包含指定 session ID 為名的資料,如果沒有就先建立一個空陣列到 <code>queued[${sessionId}]</code> 中。</p>
<p>第三段就將新建立的 <code>Cookie</code> object 放入 <code>queued[${sessionId}]['/']</code> 中。(<code>getPath()</code> 的結果預設為 <code>'/'</code>)</p>
<p>這樣就完成 session 排入 cookie queue 了!</p>
<h2 id="補完最後一塊拼圖:你剛講的都是從一半切入耶">補完最後一塊拼圖:你剛講的都是從一半切入耶</h2>
<p>我們剛追了超級大段的 code,卻是從一半切入,但還有幾個不清楚的地方:</p>
<ul>
<li><code>StartSession</code> middleware 什麼時候觸發的?</li>
<li>排好的 cookie queue 什麼時候送出?</li>
</ul>
<h3 id="laravel-專案預設的-web-middleware-group">Laravel 專案預設的 <code>web</code> Middleware Group</h3>
<p>在 <a href="https://laravel.com/docs/7.x/middleware#middleware-groups">middleware 文件</a> 中有提到,Laravel 預設有綁定 <code>web</code> middleware group 在我們的 web route 上,位於專案的 <a href="https://github.com/laravel/laravel/blob/v7.6.0/app/Http/Kernel.php#L25-L45">app/Http/Kernel.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* The application's route middleware groups.
*
* @var array
*/</span>
<span class="token keyword">protected</span> <span class="token variable">$middlewareGroups</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
<span class="token string">'web'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token punctuation">[</span>
<span class="token comment">// 略 ...</span>
\<span class="token package">Illuminate<span class="token punctuation">\</span>Cookie<span class="token punctuation">\</span>Middleware<span class="token punctuation">\</span>AddQueuedCookiesToResponse</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token keyword">class</span><span class="token punctuation">,</span>
\<span class="token package">Illuminate<span class="token punctuation">\</span>Session<span class="token punctuation">\</span>Middleware<span class="token punctuation">\</span>StartSession</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token keyword">class</span><span class="token punctuation">,</span>
<span class="token comment">// 略 ...</span>
<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token comment">// 略 ...</span>
</code></pre>
<p>這裡就是 <code>StartSession</code> 的切入點了!第一個問題解決。</p>
<p>另外我們也看到了 <code>AddQueuedCookiesToResponse</code>!</p>
<h3 id="addqueuedcookiestoresponse:將-cookie-queue-加到-response"><code>AddQueuedCookiesToResponse</code>:將 Cookie Queue 加到 Response</h3>
<p>來看看 <code>AddQueuedCookiesToResponse</code> 裡面做什麼,位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php#L28-L44">framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php</a>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token operator">*</span><span class="token operator">*</span>
<span class="token operator">*</span> Handle an incoming request<span class="token punctuation">.</span>
<span class="token operator">*</span>
<span class="token operator">*</span> @param \<span class="token package">Illuminate<span class="token punctuation">\</span>Http<span class="token punctuation">\</span>Request</span> <span class="token variable">$request</span>
<span class="token operator">*</span> @param \<span class="token package">Closure</span> <span class="token variable">$next</span>
<span class="token operator">*</span> @<span class="token keyword">return</span> mixed
<span class="token operator">*</span><span class="token operator">/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">handle</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token punctuation">,</span> Closure <span class="token variable">$next</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$response</span> <span class="token operator">=</span> <span class="token variable">$next</span><span class="token punctuation">(</span><span class="token variable">$request</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">cookies</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getQueuedCookies</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> <span class="token variable">$cookie</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$response</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">headers</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">setCookie</span><span class="token punctuation">(</span><span class="token variable">$cookie</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token variable">$response</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>重點很明顯是 <code>getQueuedCookies()</code> 和 <code>setCookie()</code> 了,簡單帶過一下。</p>
<p><code>getQueuedCookies()</code> 使用 <a href="https://laravel.com/docs/7.x/helpers#method-array-flatten">Arr::flatten()</a> helper 攤平 cookie queue 的陣列:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Cookie/CookieJar.php#L208-L216">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Get the cookies which have been queued for the next request.
*
* @return \Symfony\Component\HttpFoundation\Cookie[]
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">getQueuedCookies</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> Arr<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">flatten</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">queued</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p><code>setCookie()</code> 則是在 Symfony <sup class="footnote-ref"><a href="#fn4" id="fnref4">4</a></sup> 的 class <code>ResponseHeaderBag</code> 中,將 cookie 加到 response header 裡:(<a href="https://github.com/symfony/http-foundation/blob/v5.0.8/ResponseHeaderBag.php#L180-L184">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">setCookie</span><span class="token punctuation">(</span>Cookie <span class="token variable">$cookie</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">cookies</span><span class="token punctuation">[</span><span class="token variable">$cookie</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getDomain</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token variable">$cookie</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token variable">$cookie</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token variable">$cookie</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">headerNames</span><span class="token punctuation">[</span><span class="token string">'set-cookie'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'Set-Cookie'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>到這裡,我們兩個疑問都解決了!</p>
<h2 id="延伸討論:怎樣的字串才是-laravel-的有效-session-id?">延伸討論:怎樣的字串才是 Laravel 的有效 Session ID?</h2>
<p>雖然沒有執行到 <code>isValidId()</code> 中的後續判斷,但我們可以討論一下 Laravel 是如何判斷有效 session ID,再看一次原始碼:(<a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Session/Store.php#L565-L574">原始碼位置</a>)</p>
<pre class=" language-php"><code class="prism language-php"><span class="token comment">/**
* Determine if this is a valid session ID.
*
* @param string $id
* @return bool
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">isValidId</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token function">is_string</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token function">ctype_alnum</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token function">strlen</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token number">40</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>有效的 session ID 條件有三個:</p>
<ol>
<li>session ID 必須要是字串</li>
<li>該字串必須要是 alphanumeric,意即只能由字母和數字組成(<a href="https://www.php.net/manual/en/function.ctype-alnum.php"><code>ctype_alnum</code> 文件</a>)</li>
<li>該字串的長度必須是 40</li>
</ol>
<h2 id="後面來談談:為什麼-laravel-不使用-php-原生的-session-機制">後面來談談:為什麼 Laravel 不使用 PHP 原生的 session 機制</h2>
<p>除了原始碼的註解提到了 PHP 原生的 session 機制 crappy 外,Laravel 之父 Taylor Otwell 在 2014 年時的一個 <a href="https://github.com/laravel/framework/issues/5416#issuecomment-68366445">Issue</a> 提到:</p>
<blockquote>
<p>Using native PHP sessions is not a good solution. Native PHP sessions output cookies straight to the headers and there is no way to change that. It breaks the entire request/response wrapping we are trying to achieve.<br>
[name=Taylor Otwell]</p>
</blockquote>
<p>另外,Laravel 4.1 推出時的 <a href="https://laravel.com/docs/4.2/releases#laravel-4.1">Release Notes</a>,在 Improved Session Engine 段落也提及:</p>
<blockquote>
<p>With this release, we’re also introducing an entirely new session engine. Similar to the routing improvements, the new session layer is leaner and faster. We are no longer using Symfony’s (and therefore PHP’s) session handling facilities, and are using a custom solution that is simpler and easier to maintain.</p>
</blockquote>
<p>這裡我的解讀是,Laravel 希望能針對 Session 有更多的彈性與掌握權,並且用更具 Laravel 的風格整合在核心中。也因為包裹成 middleware,可以在寫入的前後自由地增加業務邏輯。</p>
<p>你有什麼想法嗎?歡迎留言來討論。</p>
<h2 id="總結">總結</h2>
<p><img src="https://i.imgur.com/z04d3jE.png" alt=""></p>
<p><s>結論就是我好累,為了這篇文章熬夜了多久…</s></p>
<p>統整一下我們增加的 <s>奇怪</s> Laravel 知識:</p>
<ul>
<li>Laravel 並沒有在任何一個地方使用 PHP 原生的 session 機制</li>
<li>Laravel 的 session ID 必須是 40 個英數字混合的字串</li>
<li>Laravel 會使用作業系統層級的安全隨機字串 function 自動生成 session ID</li>
<li>Laravel 預設 <strong>不</strong> 加密 session 中的 data,但可以簡單開啟加密功能</li>
<li>Laravel 也是用 middleware 處理 session</li>
<li>Laravel 預設將 session information 存在檔案中,但也能選擇各種 driver,像是跟 Rails 一樣的 cookie-based session,或是儲存在 DB 或 Redis/Memcached 中。也可以自行擴充 driver。</li>
</ul>
<h3 id="參考資料">參考資料</h3>
<p>在追 code 卡關時,感謝以下資料的提點:</p>
<ul>
<li>Reddit: <a href="https://www.reddit.com/r/laravel/comments/30qouv/why_doesnt_laravel_have_a_native_php_session/">Why doesn’t Laravel have a native PHP session driver? : laravel</a></li>
<li>Laravel China: <a href="https://learnku.com/articles/6789/starting-and-running-source-analysis-of-laravel-session-session">Laravel Session——session 的启动与运行源码分析</a></li>
<li>Laravel China: <a href="https://learnku.com/docs/the-laravel-way/5.6/startsession-of-routing-middleware/4365">路由中间件之 StartSession |《Laravel 之道 5.6》</a></li>
<li>Laravel China: <a href="https://learnku.com/articles/16638">Laravel 源码阅读指南 – Cookie 源码解析</a></li>
</ul>
<h3 id="感謝-huli">感謝 Huli</h3>
<p>Huli 對於教學與分享的熱情,一直是我崇拜的偶像(很老氣的用詞但想不到更好的 🤣)</p>
<p>在此附上 Huli 的系列文連結,感謝開啟這個系列讓我成長。</p>
<ol>
<li><a href="https://medium.com/@hulitw/session-and-cookie-15e47ed838bc">白話 Session 與 Cookie:從經營雜貨店開始</a></li>
<li><a href="https://github.com/aszx87410/blog/issues/45">淺談 Session 與 Cookie:一起來讀 RFC</a></li>
<li><a href="https://github.com/aszx87410/blog/issues/46">深入 Session 與 Cookie:Express、PHP 與 Rails 的實作</a></li>
</ol>
<p>也恭喜 Huli 找到新工作 🎉</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>另一個寫法是採用 <a href="https://laravel.com/docs/7.x/helpers#method-session"><code>session()</code></a> global helper,大同小異,原始碼位於 <a href="https://github.com/laravel/framework/blob/v7.9.2/src/Illuminate/Foundation/helpers.php#L799-L821">framework/src/Illuminate/Foundation/helpers.php</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>如果我們專案設定 session 要加密的話,這個 function 會執行加密的動作。 <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn3" class="footnote-item"><p>另一個 PHP Framework,Laravel 底層基於 Symfony。精確來說,這裡是用到 Symfony 的 <a href="https://packagist.org/packages/symfony/http-foundation">symfony/http-foundation</a> 套件的 class。 <a href="#fnref3" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn4" class="footnote-item"><p>精確來說,是 Symfony 的 <a href="https://packagist.org/packages/symfony/http-foundation">symfony/http-foundation</a> 套件。 <a href="#fnref4" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
<br />
<br />
<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license"><img alt="創用 CC 授權條款" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" style="border-width: 0;" /></a><br />
本著作由<a href="https://goodjack.blogspot.com/" property="cc:attributionName" rel="cc:attributionURL" xmlns:cc="http://creativecommons.org/ns#">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license">創用CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-58253886723500233172020-04-27T20:34:00.001+08:002020-04-27T20:34:30.291+08:00[Python 3] 產生 ISO 8601 含時區格式的現在時間(不透過 pytz)<p><img src="https://images.pexels.com/photos/48770/business-time-clock-clocks-48770.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260" alt="Business Time Clocks"></p>
<p>真的很崩潰,對 Python 不熟做個筆記 QQ</p>
<p>我想在 AWS Lambda 產生現在時間的字串,條件如下:</p>
<ul>
<li>ISO 格式(ISO 8601)</li>
<li>指定時區(例如台灣的 <code>GMT+8</code> 或 <code>Asia/Taipei</code>)</li>
<li>不要另外裝 Library</li>
</ul>
<p>預期字串如下:</p>
<pre class=" language-python"><code class="prism language-python"><span class="token string">'2020-04-27T18:18:30.810511+08:00'</span>
</code></pre>
<p>真的是出乎意料,如此簡單的需求,幾乎所有查到的資料都叫我裝 <code>pytz</code> 🤦♂️</p>
<h2 id="解決方案">解決方案</h2>
<p>花了我半個小時查資料加不停的嘗試,才找到原生可以支援的寫法:</p>
<pre class=" language-python"><code class="prism language-python"><span class="token keyword">from</span> datetime <span class="token keyword">import</span> datetime<span class="token punctuation">,</span> timezone<span class="token punctuation">,</span> timedelta
<span class="token comment"># 設定為 +8 時區</span>
tz <span class="token operator">=</span> timezone<span class="token punctuation">(</span>timedelta<span class="token punctuation">(</span>hours<span class="token operator">=</span><span class="token operator">+</span><span class="token number">8</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token comment"># 取得現在時間、指定時區、轉為 ISO 格式</span>
datetime<span class="token punctuation">.</span>now<span class="token punctuation">(</span>tz<span class="token punctuation">)</span><span class="token punctuation">.</span>isoformat<span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre>
<p>如果進一步想把微秒拿掉,可以參考 Python 文件的 <a href="https://docs.python.org/3/library/datetime.html#datetime.datetime.isoformat"><code>isoformat()</code></a> 段落,調整為:</p>
<pre class=" language-python"><code class="prism language-python">datetime<span class="token punctuation">.</span>now<span class="token punctuation">(</span>tz<span class="token punctuation">)</span><span class="token punctuation">.</span>isoformat<span class="token punctuation">(</span>timespec<span class="token operator">=</span><span class="token string">"seconds"</span><span class="token punctuation">)</span>
</code></pre>
<h2 id="心得">心得</h2>
<p>大家(包含我也是 😅)實在是太依賴套件了啊!</p>
<br />
<br />
<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license"><img alt="創用 CC 授權條款" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" style="border-width: 0;" /></a><br />
本著作由<a href="https://goodjack.blogspot.com/" property="cc:attributionName" rel="cc:attributionURL" xmlns:cc="http://creativecommons.org/ns#">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license">創用CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-59294790799680191302019-07-30T16:49:00.001+08:002019-07-30T16:49:36.108+08:00[買起來書單] 那些最常在 Stack Overflow 被提到的 30 本工程師必備書籍<p>最近 Working Effectively with Legacy Code 終於有了正體中文版,才想到是該來整理一篇了。</p>
<p>有國外網友用爬蟲抓了超過四千萬篇 Stack Overflow 的問題,統計網友在這些解答中提到的書籍。這次的新翻譯本就是本次列表中的第一名,可見其在國外網友的重要性。雖然有些書籍看似過時,但還是有很多歷久不衰的經典書籍值得收藏。</p>
<p>統計結果排名如下:<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup><br>
(以中文版的書籍名為主,簡介與書籍封面來自<a href="https://www.tenlong.com.tw">天瓏網路書店</a>)</p>
<p><div class="toc">
<ul>
<li>
<ul>
<li><a href="#working-effectively-with-legacy-code--管理、修改、重構遺留程式碼的藝術-中文版">#1 Working Effectively with Legacy Code : 管理、修改、重構遺留程式碼的藝術 (中文版)</a></li>
<li><a href="#物件導向設計模式-可再利用物件導向軟體之要素">#2 物件導向設計模式-可再利用物件導向軟體之要素</a></li>
<li><a href="#無瑕的程式碼-敏捷軟體開發技巧守則">#3 無瑕的程式碼-敏捷軟體開發技巧守則</a></li>
<li><a href="#java-concurrency-in-practice">#4 Java concurrency in practice</a></li>
<li><a href="#領域驅動設計:軟體核心複雜度的解決方法">#5 領域驅動設計:軟體核心複雜度的解決方法</a></li>
<li><a href="#javascript-優良部份">#6 JavaScript-優良部份</a></li>
<li><a href="#企業應用架構模式">#7 企業應用架構模式</a></li>
<li><a href="#code-complete:軟體開發實務指南">#8 CODE COMPLETE:軟體開發實務指南</a></li>
<li><a href="#重構|改善既有程式的設計">#9 重構|改善既有程式的設計</a></li>
<li><a href="#深入淺出設計模式">#10 深入淺出設計模式</a></li>
<li><a href="#c-語言程式設計">#11 C 語言程式設計</a></li>
<li><a href="#effective-c--改善程序與設計的-55-個具體做法">#12 Effective C++ : 改善程序與設計的 55 個具體做法</a></li>
<li><a href="#test-driven-development">#13 Test-Driven Development</a></li>
<li><a href="#演算法導論">#14 演算法導論</a></li>
<li><a href="#精通正規表達式">#15 精通正規表達式</a></li>
<li><a href="#clr-via-c">#16 CLR Via C</a></li>
<li><a href="#cocoa-programming-for-mac-os-x">#17 Cocoa Programming for Mac OS X</a></li>
<li><a href="#effective-stl-50-specific-ways-to-improve-your-use-of-the-standard-template-library">#18 Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library</a></li>
<li><a href="#modern-c-design-generic-programming-and-design-patterns-applied">#19 Modern C++ Design: Generic Programming and Design Patterns Applied</a></li>
<li><a href="#大型-c-軟體設計">#20 大型 C++ 軟體設計</a></li>
<li><a href="#inside-the-microsoft-build-engine-using-msbuild-and-team-foundation-build">#21 Inside the Microsoft Build Engine: Using MSBuild and Team Foundation Build</a></li>
<li><a href="#programming-asp.net-core">#22 Programming ASP.NET Core</a></li>
<li><a href="#xunit-test-patterns-refactoring-test-code">#23 xUnit Test Patterns: Refactoring Test Code</a></li>
<li><a href="#concurrent-programming-on-windows">#24 Concurrent Programming on Windows</a></li>
<li><a href="#編譯原理">#25 編譯原理</a></li>
<li><a href="#framework-design-guidelines-conventions-idioms-and-patterns-for-reusable-.net-libraries">#26 Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries</a></li>
<li><a href="#c-編程規範:101-個準則、指導方針,和最佳實踐">#27 C++ 編程規範:101 個準則、指導方針,和最佳實踐</a></li>
<li><a href="#unix-網絡編程">#28 UNIX 網絡編程</a></li>
<li><a href="#purely-functional-data-structures">#29 Purely Functional Data Structures</a></li>
<li><a href="#單元測試的藝術">#30 單元測試的藝術</a></li>
</ul>
</li>
</ul>
</div></p>
<hr>
<h2 id="working-effectively-with-legacy-code--管理、修改、重構遺留程式碼的藝術-中文版">#1 Working Effectively with Legacy Code : 管理、修改、重構遺留程式碼的藝術 (中文版)</h2>
<blockquote>
<p>原文書名:Working Effectively with Legacy Code<br>
作者:Michael C. Feathers<br>
<a href="https://www.tenlong.com.tw/products/9789864344000">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/138/400/original/MP11914.jpg?1563786524" alt="Working Effectively with Legacy Code : 管理、修改、重構遺留程式碼的藝術 (中文版)" width="250"></p>
<p>【名家名著】19<br>
Robert C. Martin Series</p>
<p>軟體工程師必讀的十大好書之一<br>
《無瑕的程式碼》作者 Bob 大叔親自撰文推薦!</p>
<p>遺留程式碼是每個 coder 在職場上幾乎都會遇到的難題,<br>
且讓本書幫助您披荊嶄棘,殺出一條軟體變更的康莊大道!</p>
<p>讓你更能妥善處理你的遺留程式碼:使它有更多表現、更多功能、更具可依賴性以及更易於管理</p>
<p>你的程式碼容易修改嗎?當你修改它的時候,可以即時得到回饋嗎?你了解它的運作嗎?如果有任何一個答案為否定,那麼你面對的就是遺留程式碼,而且它會拖垮你的開發成效,使你浪費更多寶貴的時間與金錢。</p>
<p>在本書中,MICHAEL C. FEATHERS 提供了許多整套的策略,使讀者能夠更有效率地使用遺留程式碼,以及未經測試的遺留 code base。這本書取材於作者為他知名的 Object Mentor 研討會所撰寫的內容。作者在指導時所提到的技巧,已經幫助數以百計的開發者、技術管理者以及測試人員,使他們的遺留系統維持在掌控之中。</p>
<p>本書內容包括:<br>
☛ 了解軟體修改的機制:增加特性、修正 bug、改善設計、調整性能。<br>
☛ 使遺留程式碼進入測試控制工具(test harness)。<br>
☛ 編寫測試來保護你免於引入新的問題。<br>
☛ 可用於任何語言或平台的技術 ── 以 Java、C++、C 與 C# 為範例。<br>
☛ 正確分辨程式碼應該修改的地方。<br>
☛ 處理非物件導向的遺留系統。<br>
☛ 處理看來似乎沒有任何結構的應用程式。</p>
<p>這本書也包含 24 個解依賴技術的目錄,可協助你讓程式的要素獨立,並且使變動更加安全。</p>
<hr>
<h2 id="物件導向設計模式-可再利用物件導向軟體之要素">#2 物件導向設計模式-可再利用物件導向軟體之要素</h2>
<blockquote>
<p>原文書名:Design Patterns: Elements of Reusable Object-Oriented Software<br>
作者:Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides<br>
<a href="https://www.tenlong.com.tw/products/9789572054116">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets1.tenlong.com.tw/images/85778/medium/9572054112.jpg" alt="物件導向設計模式-可再利用物件導向軟體之要素"></p>
<p>本書探討的是在物件導向軟體設計領域裡,用以描述對特定問題精簡優雅的解決方案:</p>
<p>設計模式。主要分兩個部份,</p>
<p>第一、它展示 patterns 在建構複雜系統時可以扮演什麼腳色模式;</p>
<p>第二、它提供非常實用的參考資料讓軟體開發者可以將這些精良的 patterns 運用在自己的專案上。作者以豐富的物件導向軟體設計經驗,將頻繁出現卻又非常精簡的解決方案彙編成型錄,因此書籍一出版立刻成為物件導向圈內人手一冊的聖經。</p>
<hr>
<h2 id="無瑕的程式碼-敏捷軟體開發技巧守則">#3 無瑕的程式碼-敏捷軟體開發技巧守則</h2>
<blockquote>
<p>原文書名:Clean Code: A Handbook of Agile Software Craftsmanship<br>
作者:Robert C. Martin<br>
<a href="https://www.tenlong.com.tw/products/9789862017050">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets1.tenlong.com.tw/images/74504/medium/PG21219.jpg" alt="無瑕的程式碼-敏捷軟體開發技巧守則"></p>
<p>本書榮獲 iThome 年度百大好書推薦</p>
<p>本書的原文書名為《Clean Code: A Handbook of Agile Software Craftsmanship》,根據作者的說法,《無瑕的程式碼》為 Jolt 得獎著作《敏捷軟體開發:原則、樣式及實務》的前傳。<br>
在台灣另一本銷售極佳的書籍《重構 ─ 改善既有程式的設計》,根據亞馬遜 Amazon 網站的統計,購買該書原文版《Refactoring: Improving the Design of Existing Code》,又同時購買的其他書籍第一名,正是《Clean Code: A Handbook of Agile Software Craftsmanship》這一本書。</p>
<p>第一章作者開宗明義說明什麼是 Clean Code,他詢問了包含 C++ 發明人 Bjarne Stroustrup、Eclipse 策略教父 Dave Thomas、極限程式設計大師 Ron Jeffries、維基與極限程式設計發明人,Ward Cunningham 等等的大師,從他們的眼光來描述什麼是 Clean Code,最後才說到作者本人認為的 Clean Code 應該長成什麼樣子,有什麼好處,以及學習撰寫 Clean Code 的基本原則。</p>
<p>小編只能說,能和那麼多大師對談,就已經證明了作者也是一位大師</p>
<p>其餘部分本書分成三部份。</p>
<p>第一部份</p>
<p>包含有許多章節,這些章節將描述撰寫 clean code 的原則、模式及實踐,這些章節包含了不少的程式碼篇幅,閱讀它們頗具有挑戰性。這些章節替你準備好閱讀第二部份所需的背景知識。</p>
<p>第二部份</p>
<p>這裡包含許多複雜性不斷增加的案例討論。在這個部分,作者會直接帶領您進行整個重構的過程,每次的修改,作者都會說明原因,重構的對象並不僅止於一般的程式,甚至還包含了著名的框架,例如 JUnit(哇!把開放原始碼框架拿來重構,真酷)。</p>
<p>第三部份</p>
<p>終於到了成果收割的時候。最後一章列出了案例討論時搜集到的程式啟發和氣味。在案例討論裡走過和清理程式碼時,紀錄了每個行為的原因,並整理成一種程式啟發或氣味,成了一個知識庫,這個知識庫可以說是整本書的精華與交互參照。</p>
<hr>
<h2 id="java-concurrency-in-practice">#4 Java concurrency in practice</h2>
<blockquote>
<p>(無中文版)<br>
作者:Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, Doug Lea<br>
<a href="https://www.tenlong.com.tw/products/9780321349606">天瓏連結(原文書)</a></p>
</blockquote>
<p>Threads are a fundamental part of the Java platform. As multicore processors become the norm, using concurrency effectively becomes essential for building high-performance applications. Java SE 5 and 6 are a huge step forward for the development of concurrent applications, with improvements to the Java Virtual Machine to support high-performance, highly scalable concurrent classes and a rich set of new concurrency building blocks. In Java Concurrency in Practice, the creators of these new facilities explain not only how they work and how to use them, but also the motivation and design patterns behind them.</p>
<p>However, developing, testing, and debugging multithreaded programs can still be very difficult; it is all too easy to create concurrent programs that appear to work, but fail when it matters most: in production, under heavy load. Java Concurrency in Practice arms readers with both the theoretical underpinnings and concrete techniques for building reliable, scalable, maintainable concurrent applications. Rather than simply offering an inventory of concurrency APIs and mechanisms, it provides design rules, patterns, and mental models that make it easier to build concurrent programs that are both correct and performant.</p>
<p>This book covers:</p>
<ul>
<li>Basic concepts of concurrency and thread safety</li>
<li>Techniques for building and composing thread-safe classes</li>
<li>Using the concurrency building blocks in java.util.concurrent</li>
<li>Performance optimization dos and don’ts</li>
<li>Testing concurrent programs</li>
<li>Advanced topics such as atomic variables, nonblocking algorithms, and the Java Memory Model</li>
</ul>
<hr>
<h2 id="領域驅動設計:軟體核心複雜度的解決方法">#5 領域驅動設計:軟體核心複雜度的解決方法</h2>
<blockquote>
<p>原文書名:Domain-Driven Design: Tackling Complexity in the Heart of Software<br>
作者:Eric Evans<br>
<a href="https://www.tenlong.com.tw/products/9789864343874">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/130/774/medium/9789864343874_bc.jpg?1554177163" alt="領域驅動設計:軟體核心複雜度的解決方法"></p>
<p>【名家名著 18】 <br>
領域驅動設計:軟體核心複雜度的解決方法</p>
<p>Domain-Driven Design: Tackling Complexity in the Heart of Software</p>
<p>『這本書應該出現在每位軟體開發人員的書架上。』(This book belongs on the shelf of every thoughtful software developer.)<br>
-Kent Beck<br>
軟體大師,JUnit 的創始人,XP 與 TDD 專家</p>
<p>『Eric 的這本書太棒、太神奇了,他準確地告訴你如何讓軟體設計滿足你的模型需求…… 本書讀起來趣味無窮。Eric 有許多有趣的故事,而且描述起來很有一套…… 它將成為軟體開發人員必讀的經典之作。』<br>
-Ralph Johnson<br>
GoF 的《Design Patterns》的作者</p>
<p>『如果你認為自己在物件導向程式設計中的投入沒有得到回報,讀了這本書你就會知道自己漏掉什麼。』<br>
-Ward Cunningham<br>
設計模式和敏捷軟體開發方法的先驅</p>
<p>『Eric Evans 成功證明了作為開發核心的領域模型的重要性。他搭建了一個穩固的框架,並提供一套實作技術和技巧。這裡沉澱下來的是亙古不變的智慧,在流行的方法論都淪為明日黃花之後,它依然光華璀璨。』<br>
-Dave Collins<br>
《Designing Object-Oriented User Interfaces》的作者</p>
<p>『Eric 完全從實戰者的角度著手,描述了通用的語言、與使用者共享模型的好處、物件生命週期的管理、深度重構的過程和結果,這是對我們這個領域的巨大貢獻。』<br>
-Luke Hohmann<br>
《Beyond Software Architecture》的作者</p>
<p>『Eric 成功抓住了經驗豐富的物件設計師在設計過程中會使用的那一部分…… 我們從未有組織地和有系統地把建立領域邏輯的原則制度化。這是一本非常重要的著作。』<br>
-Kyle Brown<br>
《Enterprise Java Programming with IBM WebSphere》的作者</p>
<p>『講解 DDD 的書籍並不多,而這本書是當中公認最好的一本。如果您對於 DDD 有任何疑問,這本書將會提供最佳解答』<br>
-陳錦輝<br>
博碩文化《名家名著》總編輯</p>
<hr>
<h2 id="javascript-優良部份">#6 JavaScript-優良部份</h2>
<blockquote>
<p>原文書名:JavaScript: The Good Parts<br>
作者:Douglas Crockford<br>
<a href="https://www.tenlong.com.tw/products/9789866840272">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets1.tenlong.com.tw/images/46646/medium/9789866840272.gif" alt="JavaScript-優良部份"></p>
<p>大多數程式語言都包含好與不好的兩類成分,但 JavaScript 的後者似乎佔了不少比例,因為它是個開發與發表都很匆促、沒有時間精雕細琢的語言。這本權威書籍刮除了 JavaScript 中最可怕的功能,為大家呈現較為可靠、較可讀解、也較可維護的部分 JavaScript - 我們能用這部分功能建立真正具擴充性、又有效率的原始碼。</p>
<p>本書作者 Douglas Crockford,一位獲程式開發社群中許多人公認的 JavaScript 專家,分辨出了讓 JavaScript 成為傑出物件導向程式設計語言的優良部分。但很可惜,這些優良的部分(例如函式、寬鬆型別、動態物件、富表達性的物件實字註記)都與糟糕到家的想法混在一起(例如以全域變數為基礎的程式設計模型)。</p>
<p>當 Java applets 失敗後,JavaScript 變成全球資訊網上的預設程式語言;它的流行程度,完全與它的程式語言品質無關。在《JavaScript:優良部分》中,Crockford 從龍蛇雜處的良好意圖與錯誤失敗中,仔細挖出 JavaScript 所有真正優雅的部分,包括:</p>
<ul>
<li>語法</li>
<li>繼承性</li>
<li>方法</li>
<li>物件</li>
<li>陣列</li>
<li>Style</li>
<li>函式</li>
<li>正規式</li>
<li>漂亮的功能</li>
</ul>
<p>一本《JavaScript:優良部分》在手,你將發現一個美麗、優雅、輕量,且高度表達性的語言,可建立有效率的程式碼;無論你在管理物件函式庫,或只是想讓 Ajax 跑得更快。如果你為全球資訊網開發網站或應用程式,一定要閱讀本書。</p>
<p>Douglas Crockford 是 Yahoo! 的資深 JavaScript 架構師,因為他對 JSON(JavaScript Object Notation)格式的引進與維護而相當知名。經常於 JavaScript 深度研討會上演講,也是 ECMAScript 委員會的成員。</p>
<hr>
<h2 id="企業應用架構模式">#7 企業應用架構模式</h2>
<blockquote>
<p>(僅有簡體版)<br>
原文書名:Patterns of Enterprise Application Architecture<br>
作者:Martin Fowler<br>
<a href="https://www.tenlong.com.tw/products/9787111303930">天瓏連結(簡體版)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/080/060/medium/51l5InFD7RL.jpg?1525641560" alt="企業應用架構模式"></p>
<p>企業應用開發的實踐得益於多種新技術的出現,多層的面向對象平臺 (如 Java、.NET) 已經日漸平常。這些新工具和新技術有能力構建更強大的企業應用程序,但是在實現上還不太容易。由於開發人員未能充分理解有經驗的對象程序開發人員在架構方面的經驗和教訓,因此企業應用中經常存在一些共同的錯誤。</p>
<p>本書就是面向企業應用開發者的,可幫助他們迎接這種艱難挑戰。本書的作者 Martin Fowler 註意到,儘管技術本身存在變化 —— 從 Smalltalk 到 CORBA,再到 Java 和 .NET,但基本的設計思想並沒有太多變化,可以加以適當調整,用來解決那些共同的問題。在一組專家級合作者的幫助下,作者將 40 多種經常出現的解決方案轉化成模式,最終寫成這本能夠應用於任何一種企業應用平臺的、關於解決方案的、不可或缺的手冊。本書曾於 2002 年榮獲美國軟件開發雜誌圖書類的生產效率獎和讀者選擇獎。</p>
<p>本書涉及兩部分內容。第一部分是關於如何開發企業應用的簡單介紹。在閱讀這部分時,讀者可以從頭到尾通讀,以掌握本書的範圍。第二部分是本書的主體,是關於模式的詳細參考手冊,每個模式都給出使用方法和實現信息,並配有詳細的 Java 代碼或 C# 代碼的示例。此外,整本書中還用大量 UML 圖來進一步闡明有關概念。</p>
<p>本書是為致力於設計和構建企業應用的軟件架構師、設計人員和編程人員而寫的,同時也可作為高等院校電腦專業及軟件學院相關課程的參考教材。</p>
<hr>
<h2 id="code-complete:軟體開發實務指南">#8 CODE COMPLETE:軟體開發實務指南</h2>
<blockquote>
<p>原文書名:Code Complete: A Practical Handbook of Software Construction<br>
作者:Steve McConnell<br>
<a href="https://www.tenlong.com.tw/products/9789864341313">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/127/011/medium/PG21401_BIG.jpg?1542262703" alt="CODE COMPLETE:軟體開發實務指南"></p>
<p>廣獲好評的程式設計實務指南,在過去十幾年間,由 Steve McConnell 所原創的《Code Complete》已幫助許許多多的開發者,開發出更完善的軟體。本書包含最新的更新與修訂內容,以及數百項全新的程式碼範例,充分闡明軟體建構的科學與藝術。McConnell 匯集了來自學術研究與商業實務的各項知識,歸納出最有效的工作技巧、以及最關鍵的實踐原則,轉化成一系列清晰且務實的指引。無論你的知識水準、工作環境或專案規模如何,本書都能提昇你的智慧,幫助你建構出最佳品質的程式碼。</p>
<p>發掘各項歷久彌新的技巧與策略,使你能夠:</p>
<ul>
<li>建構出低複雜性,但卻有高可塑性的設計。</li>
<li>組織健全的協作式開發並從中獲益。</li>
<li>應用防禦性程式設計技巧,消弭發生錯誤的機會。</li>
<li>洞察合適的時機,妥善地對程式碼進行重構或演變。</li>
<li>依專案規模選擇合適的實踐方法。</li>
<li>快速且有效地進行除錯。</li>
<li>及早且正確地解決重大的建構問題。</li>
<li>從一而終保持高品質的軟體專案。</li>
</ul>
<hr>
<h2 id="重構|改善既有程式的設計">#9 重構|改善既有程式的設計</h2>
<blockquote>
<p>原文書名:Refactoring: Improving the Design of Existing Code<br>
作者:Martin Fowler<br>
<a href="https://www.tenlong.com.tw/products/9789865021832">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/136/342/medium/ACL054600.jpg?1561522072" alt="重構|改善既有程式的設計"></p>
<p>完全修訂及更新:包含新的重構方法和範例程式</p>
<p>二十多年來,世界各地經驗豐富的程式員都使用 Martin Fowler 的《重構》來改善既有程式的設計、提升軟體的易維護性,以及讓既有的程式更容易被人瞭解。</p>
<p>為了反映程式設計領域的重大變化,作者全面翻新書籍內容,推出這本備受期待的新版本。《重構 第二版》提供了新的重構名錄,加入 JavaScript 範例程式以及新的實用範例來展示各種重構。</p>
<p>這個版本與第一版一樣,將解釋什麼是重構、為何重構、如何認出需要重構的程式,以及如何成功地重構,無論你是使用哪一種語言。</p>
<ul>
<li>瞭解重構的程序與一般原則</li>
<li>快速運用實用的重構技術,讓程式更容易理解與修改</li>
<li>辨認暗示有待重構的程式碼 “異味”</li>
<li>探討重構,每一個案例都包括說明、動機、作法與簡單的範例</li>
<li>為重構建立可靠的測試程式</li>
<li>認識重構的取捨與障礙</li>
</ul>
<p>本書原文網站提供免費的 web 標準版本,包含更多重構資源,書中內容將說明如何取得。</p>
<hr>
<h2 id="深入淺出設計模式">#10 深入淺出設計模式</h2>
<blockquote>
<p>原文書名:Head First Design Patterns<br>
作者:Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson<br>
<a href="https://www.tenlong.com.tw/products/9789867794529">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets1.tenlong.com.tw/images/32306/medium/9867794524.jpg" alt="深入淺出設計模式"></p>
<p>你不想重新發明輪子(或者更差的是,沒有充氣的輪子),所以你從設計模式中尋求協助-設計模式是過去人們面對軟體設計問題所學來的經驗。</p>
<p>有了設計模式,你就可以利用其他人經驗和實務的精華,省下的時間可以用在… 其他的事情上,像是:更有挑戰性的事情、更複雜的事情、 更有趣的事情。</p>
<p>你想要學習:</p>
<p>事關緊要的模式<br>
何時使用某個模式,為何使用該模式<br>
如何在你自己的設計中馬上採用這些模式<br>
何時不該使用模式(如何避免對模式過度狂熱)<br>
某個模式是依據哪些 OO 設計守則而設計出來的<br>
更重要的是,你想在學習設計模式的過程中,不會感覺到昏昏欲睡。</p>
<p>如果你曾經讀過任何一本一頭栽進系列書籍, 你就會知道你能夠從本書中得到的是:透過豐富的視覺效果讓你的大腦充分地運作。</p>
<p>本書的編寫運用許多最新的研究, 包括神經生物學、認知科學、以及學習理論,這使得這本書能夠將這些設計模式深深地烙印在你的腦海中,不容易被遺忘。</p>
<p>你將會更擅長於解決軟體設計的問題,並能夠和你的團隊成員用模式的術語來溝通。</p>
<hr>
<h2 id="c-語言程式設計">#11 C 語言程式設計</h2>
<blockquote>
<p>原文書名:The C Programming Language<br>
作者:Brian W. Kernighan, Dennis M. Ritchie<br>
<a href="https://www.tenlong.com.tw/products/9789861541716">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets1.tenlong.com.tw/images/114880/medium/1484563439584.jpg" alt="C 語言程式設計"></p>
<ul>
<li>詳細探討 C 語言的基本資料型別、運算子和運算式</li>
<li>內容涵蓋函數和程式結構、外變數、範圍規則等介紹</li>
<li>指標、結構、運算符號集、及標準程式庫等重要特性說明</li>
<li>提供完整且經過測試的程式範例</li>
<li>內含 C 語言參考手冊及最新 C99 標準</li>
<li>詳細探討 C 語言的基本資料型別、運算子和運算式</li>
<li>內容涵蓋函數和程式結構、外變數、範圍規則等介紹</li>
<li>指標、結構、運算符號集、及標準程式庫等重要特性說明</li>
<li>提供完整且經過測試的程式範例</li>
<li>內含 C 語言參考手冊及最新 C99 標準</li>
</ul>
<hr>
<h2 id="effective-c--改善程序與設計的-55-個具體做法">#12 Effective C++ : 改善程序與設計的 55 個具體做法</h2>
<blockquote>
<p>原文書名:Effective C++: 55 Specific Ways to Improve Your Programs and Designs<br>
作者:Scott Meyers<br>
<a href="https://www.tenlong.com.tw/products/9789861543550">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets1.tenlong.com.tw/images/31211/medium/9861543554.jpg" alt="Effective C++ : 改善程序與設計的 55 個具體做法"></p>
<p>《Effective C++》前兩個版本抓住了全世界無數程式員的目光。原因十分顯明:Scott Meyers 極富實踐意義的 C++ 研討方式,描述出專家用以產出乾淨、正確、高效程式碼的經驗法則和行事法則 — 也就是他們幾乎總是做或不做的某些事。</p>
<p>本書一共組織 55 個準則,每一條準則描述一個編寫出更好的 C++ 的方式。每一個條款的背後都有具體範例支撐。第三版有一半以上的篇幅是嶄新內容,包括討論資源管理和模板(templates)運用的兩個新章。為反映出現代設計考量,對第二版論題做了廣泛的修訂,包括異常(exceptions)、設計範式(design patterns)和多緒(multithreading)。</p>
<p>《Effective C++》的重要特徵包括:</p>
<ul>
<li>高效的 classes、functions、templates 和 inheritance hierarchies(繼承體系)方面的專家級指導。</li>
<li>嶄新的 “TR1” 標準程式庫功能應用,以及與既有標準程式庫組件的比較。</li>
<li>洞察 C++ 和其他語言(例如 Java、#C、C)之間的不同。此舉有助於那些來自其他語言陣營的開發人員消化吸收 C++ 式的各種解法。</li>
</ul>
<hr>
<h2 id="test-driven-development">#13 Test-Driven Development</h2>
<blockquote>
<p>(無中文版)<br>
作者:Kent Beck<br>
<a href="https://www.tenlong.com.tw/products/9780321146533">天瓏連結(原文書)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/014/756/medium/51IraByqmSL.jpg?1525623635" alt="Test-Driven Development"></p>
<p>Test-driven development (TDD) is a new approach to application development that is designed to eliminate the fear often associated with building software. Admittedly, some fear is healthy (often viewed as a conscience that tells programmers to “be careful!” ), but the author believes that programmers build better software when they have the freedom to be creative. By building tests before coding begins, programmers ensure the success of their application from the outset. Students are more likely to achieve positive results with TDD. The author’s example-driven approach also teaches students to be better communicators, and encourages team members to seek out constructive criticism.</p>
<hr>
<h2 id="演算法導論">#14 演算法導論</h2>
<blockquote>
<p>(僅有簡體版,已絕版)<br>
原文書名:Introduction to Algorithms<br>
作者:Martin Fowler<br>
<a href="https://www.tenlong.com.tw/products/9780262033848">天瓏連結(原文書)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/060/879/medium/41-1VkO_1lL.jpg?1525598746" alt="演算法導論"></p>
<p>本書深入淺出,全面地介紹了計算機演算法。對每一個演算法的分析既易於理解又十分有趣,並保持了數學嚴謹性。本書的設計目標全面,適用於多種用途。涵蓋的內容有:演算法在計算中的作用,概率分析和隨機演算法的介紹。本書專門討論了線性規劃,介紹了動態規劃的兩個應用,隨機化和線性規劃技術的近似演算法等,還有有關遞歸求解、快速排序中用到的劃分方法與期望線性時間順序統計演算法,以及對貪心演算法元素的討論。本書還介紹了對強連通子圖演算法正確性的證明,對哈密頓回路和子集求和問題的 NP 完全性的證明等內容。全書提供了 900 多個練習題和思考題以及敘述較為詳細的實例研究。</p>
<p>本書內容豐富,對本科生的資料結構課程和研究生的演算法課程都是很實用的教材。本書在讀者的職業生涯中,也是一本案頭的數學參考書或工程實踐手冊。</p>
<hr>
<h2 id="精通正規表達式">#15 精通正規表達式</h2>
<blockquote>
<p>原文書名:Mastering Regular Expressions<br>
作者:Jeffrey E.F. Friedl<br>
<a href="https://www.tenlong.com.tw/products/9789862764404">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets1.tenlong.com.tw/images/69580/medium/A308.jpg" alt="精通正規表達式"></p>
<p>涵蓋 Perl, PHP, Java, .NET, Ruby 以及其他語言!</p>
<p>理解您的資料,增進生產力</p>
<p>這本書將帶您熟悉一套能讓您生活輕鬆許多的生產力秘密:正規表達式 (regular expressions)。精細雕琢的正規表達式可以把數小時的勞力工作化為 15 秒搞定的解決法。現在它已經是許多語言、著名工具的標準功能 —Perl, PHP, Java, Python, Ruby, MySQL, <a href="http://VB.NET">VB.NET</a> 與 C# (還有任何使用 .NET Framework 的語言) 都有提供這個功能。正規表達式將能讓您寫出複雜、精細的文字處理工作,這些工作可能是您以往絕不認為可以自動化的。</p>
<p>《精通正規表達式 第三版》納入 PHP 以及它提供的強大正規表達式功能。這個版本同時也徹底更新以反應其他語言的進展,像是擴充了深入討論 Sun 的 java.util.regex 相關內容,同時也特別提到許多 Java 1.4.2 與 Java 1.5/1.6 之間的差異。</p>
<p>本書涵蓋主題包括:</p>
<ul>
<li>比較各種語言與工具提供的功能</li>
<li>正規表達式引擎如河運作</li>
<li>最佳化 (效果明顯!)</li>
<li>只比對您要的資料,略過您不要的部份</li>
<li>各語言獨立的章節探討</li>
</ul>
<p>全書以明快、有趣的風格撰寫,讓程式設計者能清楚理解這個複雜的主題,同時也充滿各種現實世界之中難解問題的解法。《精通正規表達式 第三版》提供許多有用的資訊讓您立刻發揮,為各種問題寫出優雅、節省時間的解決方案。</p>
<hr>
<h2 id="clr-via-c">#16 CLR Via C</h2>
<blockquote>
<p>(僅有簡體版)<br>
原文書名:CLR Via C#<br>
作者:Jeffrey Richter<br>
<a href="https://www.tenlong.com.tw/products/9787302380979">天瓏連結(簡體版)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/083/916/medium/51fEutVVkHL.jpg?1525643942" alt="CLR Via C#"></p>
<p>本書針對 CLR 和 .NET Framework 4.5 進行深入、全面的探討,並結合實例介紹瞭如何利用它們進行設計、開發和調試。全書 5 部分共 29 章。第 Ⅰ 部分介紹 CLR 基礎,第 Ⅱ 部分解釋如何設計類型,第 Ⅲ 部分介紹基本類型,第 Ⅳ 部分以核心機制為主題,第 Ⅴ 部分重點介紹執行緒。</p>
<p>通過本書的閱讀,讀者可以掌握 CLR 和 .NET Framework 的精髓,輕鬆、高效地創建高性能應用程式。</p>
<hr>
<h2 id="cocoa-programming-for-mac-os-x">#17 Cocoa Programming for Mac OS X</h2>
<blockquote>
<p>(無中文版)<br>
作者:Aaron Hillegass, Adam Preble<br>
<a href="https://www.tenlong.com.tw/products/9787302380979">天瓏連結(原文書)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/061/879/medium/41pMj0Bsr1L.jpg?1525720226" alt="Cocoa Programming for Mac OS X"></p>
<p>If you’re developing applications for Mac OS X, Cocoa® Programming for Mac® OS X, Fourth Edition, is the book you’ve been waiting to get your hands on. If you’re new to the Mac environment, it’s probably the book you’ve been told to read first.</p>
<p>Covering the bulk of what you need to know to develop full-featured applications for OS X, written in an engaging tutorial style, and thoroughly class-tested to assure clarity and accuracy, it is an invaluable resource for any Mac programmer. Specifically, Aaron Hillegass and Adam Preble introduce the two most commonly used Mac developer tools: Xcode and Instruments. They also cover the Objective-C language and the major design patterns of Cocoa. Aaron and Adam illustrate their explanations with exemplary code, written in the idioms of the Cocoa community, to show you how Mac programs should be written. After reading this book, you will know enough to understand and utilize Apple’s online documentation for your own unique needs. And you will know enough to write your own stylish code.</p>
<p>Updated for Mac OS X 10.6 and 10.7, this fourth edition includes coverage of Xcode 4, blocks, view-based table views, Apple’s new approach to memory management (Automatic Reference Counting), and the Mac App Store. This edition adds a new chapter on concurrency and expands coverage of Core Animation. The book now devotes a full chapter to the basics of iOS development.</p>
<hr>
<h2 id="effective-stl-50-specific-ways-to-improve-your-use-of-the-standard-template-library">#18 Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library</h2>
<blockquote>
<p>(無中文版)<br>
作者:Scott Meyers<br>
<a href="https://www.tenlong.com.tw/products/9780201749625">天瓏連結(原文書)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/008/930/medium/416gOk71-DL.jpg?1525615587" alt="Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library"></p>
<p>C++‘s Standard Template Library is revolutionary, but learning to use it well has always been a challenge for students. In Effective STL, best-selling author Scott Meyers (Effective C++, More Effective C++) reveals the critical rules of thumb employed by the experts – the things they almost always do or almost always avoid doing – to get the most out of the library. This book offers clear, concise, and concrete guidelines to C++ programmers. While other books describe what’s in the STL, Effective STL shows the student how to use it. Each of the book’s 50 guidelines is backed by Meyers’ legendary analysis and incisive examples, so the student will learn not only what to do, but also when to do it - and why.</p>
<hr>
<h2 id="modern-c-design-generic-programming-and-design-patterns-applied">#19 Modern C++ Design: Generic Programming and Design Patterns Applied</h2>
<blockquote>
<p>(無中文版)<br>
作者:Scott Meyers<br>
<a href="https://www.tenlong.com.tw/products/9780201749625">天瓏連結(原文書)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/007/177/medium/71XmotAbKBL.jpg?1525616692" alt="# Modern C++ Design: Generic Programming and Design Patterns Applied"></p>
<p>Achieve more with C++ than you ever imagined possible!</p>
<ul>
<li>Introduces generic components, which offer breakthrough power for maximizing expressiveness, flexibility, and reuse of code.</li>
<li>Readers will learn exciting, powerful new C++ idioms that will help them master the most modern library writing techniques.</li>
<li>Forewords by Scott Meyers (Effective C++), one of the world’s leading C++ experts, and by John Vlissides, of the IBM T.J. Watson Research center, one of the world’s leading patterns experts.</li>
</ul>
<p>In Modern C++ Design, Andrei Alexandrescu opens new vistas for C++ programmers. Displaying extraordinary creativity and virtuosity, Alexandrescu offers a cutting-edge approach to software design that unites design patterns, generic programming, and C++, enabling programmers to achieve expressive, flexible, and highly reusable code. The book introduces the concept of generic components, reusable design templates that enable an easier and more seamless transition from design to application code, generate code that better expresses the original design intention, and support the reuse of design structures with minimal recoding. The author then shows how to apply this approach to recurring, real-world issues that C++ programmers face in their day-to-day activity. All code is available on the Web, along with Alexandrescu’s downloadable Loki C++ library, which provides powerful out-of-the-box functionality for virtually any C++ project. For experienced C++ programmers who have at least some familiarity with the Standard Template Library (STL).</p>
<hr>
<h2 id="大型-c-軟體設計">#20 大型 C++ 軟體設計</h2>
<blockquote>
<p>原文書名:Large-Scale C++ Software Design<br>
作者:John Lakos<br>
<a href="https://www.tenlong.com.tw/products/9789862763391">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets1.tenlong.com.tw/images/67175/medium/AXP013200.jpg" alt="大型 C++ 軟體設計"></p>
<p>開發大型 C++ 軟體系統不只需要了解邏輯設計知識,為了專案成功,您更需要掌握實體設計的觀念,它是開發過程的技術面向,很多資深的軟體開發工程師都不一定很有這方面的經驗。坊間書籍大多是探討邏輯設計,鮮少述及實體設計。</p>
<p>本書是需要大量專業 C++ 開發工作如資料庫、作業系統、編譯器以及軟體框架的權威書籍,它也是教導如何開發大型系統的第一本書,更是少數幾本由 C++ 程式語言的實際面來探討物件導向設計的其中之一。</p>
<p>在本書中,Lakos 先生將傳授將大型系統拆解成由小型、易於維護的軟體元件所構成的實體 (不是繼承) 架構。系統本身如果不具有環形實體相依性,對它維護、測試與重複利用會比對相互緊密相依的系統容易且經濟。除了闡述好的實體設計就像好的邏輯設計一樣重要之外,作者也提供一系列技巧用以消除環形、編譯時期與連結時期相依關係。作者接著會延伸這些觀念到非常非常大型的系統,而在本書後段將探討單一元件設計常見的 top-down 方式。</p>
<hr>
<h2 id="inside-the-microsoft-build-engine-using-msbuild-and-team-foundation-build">#21 Inside the Microsoft Build Engine: Using MSBuild and Team Foundation Build</h2>
<blockquote>
<p>(無中文版)<br>
作者:Sayed Hashimi, William Bartholomew<br>
<a href="https://www.tenlong.com.tw/products/9780735645240">天瓏連結(原文書)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/056/282/medium/511vjGV9iNL.jpg?1525714733" alt="Inside the Microsoft Build Engine: Using MSBuild and Team Foundation Build"></p>
<p>As software complexity increases, proper build practices become ever more important. This essential reference—fully updated for Visual Studio 2010—drills inside MSBuild and shows you how to maximize your control over the build and deployment process. Learn how to customize and extend build processes with MSBuild—and scale them to the team, product, or enterprise level with Team Foundation Build.</p>
<hr>
<h2 id="programming-asp.net-core">#22 Programming <a href="http://ASP.NET">ASP.NET</a> Core</h2>
<blockquote>
<p>(原始資料為 2.0 版,我替換成 Core 版本)<br>
(無中文版)<br>
作者:Dino Esposito<br>
<a href="https://www.tenlong.com.tw/products/9781509304417">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/106/244/medium/413_qtRE6QL.jpg?1525736259" alt="Programming ASP.NET Core"></p>
<p>Dino Esposito’s Programming <a href="http://ASP.NET">ASP.NET</a> Core is the definitive guide to practical software development with Microsoft’s exciting new <a href="http://ASP.NET">ASP.NET</a> Core technologies. Unlike competitive books that focus primarily on <a href="http://ASP.NET">ASP.NET</a> Core’s cross-platform capabilities or only what’s changed from earlier versions, Esposito offers a complete learning path for every developer who wants to build production solutions. Esposito’s expert coverage includes:</p>
<ul>
<li>Applying all key <a href="http://ASP.NET">ASP.NET</a> Core components, including MVC for HTML generation, .NET Core, EF Core, <a href="http://ASP.NET">ASP.NET</a> Identity, dependency injection, and more</li>
<li>Integrating <a href="http://ASP.NET">ASP.NET</a> Core with leading client-side frameworks, including Bootstrap</li>
<li><a href="http://ASP.NET">ASP.NET</a> Core code for implementing business logic and data transformations</li>
<li>Handling configuration, routing, controllers, views, and common tasks (including posting forms and presenting data)</li>
<li>Performing complementary tasks: error handling, logging, application design, authentication, localization, and more</li>
<li>Front-end development: ensuring responsiveness, creating mobile views, and providing advanced interactivity</li>
<li>Middleware, data access, runtime architecture, and deployment</li>
<li>Taking full advantage of the brand-new <a href="http://ASP.NET">ASP.NET</a> Core runtime</li>
</ul>
<hr>
<h2 id="xunit-test-patterns-refactoring-test-code">#23 xUnit Test Patterns: Refactoring Test Code</h2>
<blockquote>
<p>(無中文版)<br>
作者:Gerard Meszaros<br>
<a href="https://www.tenlong.com.tw/products/9780131495050">天瓏連結(原文書)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/035/855/medium/517GxwaFMvL.jpg?1525626808" alt="xUnit Test Patterns: Refactoring Test Code"></p>
<p>Automated testing is a cornerstone of agile development. Testing can deliver new functionality more aggressively, accelerate user feedback, and improve quality. However, for many developers, creating effective automated tests is a unique and unfamiliar challenge.</p>
<p>XUnit Test Patterns is the definitive guide to writing automated tests for today’s popular XUnit test automation frameworks. Renowned testing expert Gerard Meszaros introduces more than 120 proven patterns for making tests easier to write, understand, and maintain. He then shows you how to make them more robust and repeatable, and far more cost-effective.</p>
<p>Drawing on his extensive experience, Meszaros illuminates the evolving role of software testing and clearly defines unit, component and system testing. He then links these concepts to the “programmer” and “customer” tests required by agile methods. You’ll learn how to optimize your test automation strategy, organize it, and implement it with XUnit. You’ll also learn three categories of recurring problems, and how to overcome each of these “test smells.”</p>
<p>A comprehensive reference to more than 120 testing patterns is included, and Meszaros illuminates the principles underlying each pattern, offering step-by-step usage instructions. You’ll find high-level strategy patterns, design-level patterns for testing specific functionality, and coding idioms for optimizing specific tests. In addition, an extensive library of relevant code samples is available online. Topics covered include:</p>
<ul>
<li>Writing better tests–and writing them faster</li>
<li>Software testing phases: fixture setup, exercise SUT, result verification, and fixture teardown</li>
<li>Testing business logic, databases, user interfaces, and machine-to-machine interfaces</li>
<li>Isolating software to test it independently from its environment</li>
<li>Refactoring tests for greater simplicity</li>
<li>Working effectively with XUnit, NUnit, JUnit, and other implementations</li>
<li>Designing software for greater testability</li>
</ul>
<p>This book will benefit developers, managers, and testers working with any agile or conventional development process, any testing framework, and any testing strategy–from “tests as specification” to “tests as safety net.”</p>
<hr>
<h2 id="concurrent-programming-on-windows">#24 Concurrent Programming on Windows</h2>
<blockquote>
<p>(無中文版)<br>
作者:Joe Duffy<br>
<a href="https://www.tenlong.com.tw/products/9780321434821">天瓏連結(原文書)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/044/865/medium/41RLkdiboaL.jpg?1525661418" alt="Concurrent Programming on Windows"></p>
<p>Author Joe Duffy has risen to the challenge of explaining how to write software that takes full advantage of concurrency and hardware parallelism. In Concurrent Programming on Windows, he explains how to design, implement, and maintain large-scale concurrent programs, primarily using C# and C++ for Windows.</p>
<p>Duffy aims to give application, system, and library developers the tools and techniques needed to write efficient, safe code for multicore processors. This is important not only for the kinds of problems where concurrency is inherent and easily exploitable—such as server applications, compute-intensive image manipulation, financial analysis, simulations, and AI algorithms—but also for problems that can be speeded up using parallelism but require more effort—such as math libraries, sort routines, report generation, XML manipulation, and stream processing algorithms.</p>
<p>Concurrent Programming on Windows has four major sections: The first introduces concurrency at a high level, followed by a section that focuses on the fundamental platform features, inner workings, and API details. Next, there is a section that describes common patterns, best practices, algorithms, and data structures that emerge while writing concurrent software. The final section covers many of the common system-wide architectural and process concerns of concurrent programming.</p>
<p>This is the only book you’ll need in order to learn the best practices and common patterns for programming with concurrency on Windows and .NET.</p>
<hr>
<h2 id="編譯原理">#25 編譯原理</h2>
<blockquote>
<p>(僅有簡體版)<br>
原文書名:Compilers : Principles, Techniques, and Tools<br>
作者:Monica S Lam, R. Sethi, Jeffrey D. Ullman, A.V. Aho<br>
<a href="https://www.tenlong.com.tw/products/9787111251217">天龍連結(簡體版)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/063/647/medium/51Bv6BJig-L.jpg?1525632200" alt="編譯原理"></p>
<p>《編譯原理 (第 2 版)》全面、深入地探討了編譯器設計方面的重要主題,包括詞法分析、語法分析、語法制導定義和語法制導翻譯、運行時刻環境、目標代碼生成、代碼優化技術、並行性檢測以及過程間分析技術,並在相關章節中給出大量的實例。與上一版相比,《編譯原理 (第 2 版)》進行了全面的修訂,涵蓋了編譯器開發方面的最新進展。每章中都提供了大量的系統及參考文獻。 《編譯原理 (第 2 版)》是編譯原理課程方面的經典教材,內容豐富,適合作為高等院校計算機及相關專業本科生及研究生的編譯原理課程的教材,也是廣大技術人員的極佳參考讀物。</p>
<hr>
<h2 id="framework-design-guidelines-conventions-idioms-and-patterns-for-reusable-.net-libraries">#26 Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries</h2>
<blockquote>
<p>(無中文版)<br>
作者:Krzysztof Cwalina, Brad Abrams<br>
<a href="https://www.tenlong.com.tw/products/9780321545619">天瓏連結(原文書)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/044/868/medium/51cRrFPQyBL.jpg?1525628281" alt="Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries"></p>
<p>Framework Design Guidelines, Second Edition, teaches developers the best practices for designing reusable libraries for the Microsoft .NET Framework. Expanded and updated for .NET 3.5, this new edition focuses on the design issues that directly affect the programmability of a class library, specifically its publicly accessible APIs.</p>
<p>This book can improve the work of any .NET developer producing code that other developers will use. It includes copious annotations to the guidelines by thirty-five prominent architects and practitioners of the .NET Framework, providing a lively discussion of the reasons for the guidelines as well as examples of when to break those guidelines.</p>
<p>Microsoft architects Krzysztof Cwalina and Brad Abrams teach framework design from the top down. From their significant combined experience and deep insight, you will learn</p>
<ul>
<li>The general philosophy and fundamental principles of framework design</li>
<li>Naming guidelines for the various parts of a framework</li>
<li>Guidelines for the design and extending of types and members of types</li>
<li>Issues affecting–and guidelines for ensuring–extensibility</li>
<li>How (and how not) to design exceptions</li>
<li>Guidelines for–and examples of–common framework design patterns</li>
</ul>
<p>Guidelines in this book are presented in four major forms: Do, Consider, Avoid, and Do not. These directives help focus attention on practices that should always be used, those that should generally be used, those that should rarely be used, and those that should never be used. Every guideline includes a discussion of its applicability, and most include a code example to help illuminate the dialogue.</p>
<p>Framework Design Guidelines, Second Edition, is the only definitive source of best practices for managed code API development, direct from the architects themselves.</p>
<p>A companion DVD includes the Designing .NET Class Libraries video series, instructional presentations by the authors on design guidelines for developing classes and components that extend the .NET Framework. A sample API specification and other useful resources and tools are also included.</p>
<hr>
<h2 id="c-編程規範:101-個準則、指導方針,和最佳實踐">#27 C++ 編程規範:101 個準則、指導方針,和最佳實踐</h2>
<blockquote>
<p>原文書名:C++ Coding Standards: 101 Rules, Guidelines, and Best Practices<br>
作者:Herb Sutter, Andrei Alexandrescu<br>
<a href="https://www.tenlong.com.tw/products/9789861814612">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets1.tenlong.com.tw/images/45560/medium/9789861814612.jpg" alt="C++ 編程規範:101個準則、指導方針,和最佳實踐"></p>
<p>堅實而高品質的編程規範(coding standards)可以改善軟體品質,提早產品上市,助長團隊合作,把時間用在重要且合理的事情上,並且簡化維護。當今全球最受矚目的兩位 C++ 專家從全世界無數 C++ 社群的豐富經驗中提煉出一整組編程規範,讓每一位開發人員和團隊都能夠了解並用來做為他們自己的編程規範的基礎。<br>
兩位作者涵蓋了 C++ 編程的幾乎每一個面向:設計和撰碼風格(design and coding style)、函式(functions)、運算子(operators)、class 設計、繼承(inheritance)、建構 / 解構(construction/destruction)、拷貝(coping)、賦值(assignment)、命名空間(namespaces)、模塊(modules)、模板(templates)、泛型(genericity)、異常(exceptions)、STL 容器和演算法(containers and algorithms),以及更多內容,並搭配實際範例。從型別定義(type definition)到錯誤處理(error handling),本書呈現 C++ 的最佳實踐,包括某些縱使你使用 C++ 多年也不一定知曉,最近才獲確認及標準化的技術。循此方向,你將發現諸如此類的問題解答:</p>
<ul>
<li>什麼東西值得被標準化?什麼不值得?</li>
<li>什麼是寫出伸縮性程式碼(code for scalability)的最佳手段?</li>
<li>什麼是合理的錯誤處理策略(error handling policy)的要素?</li>
<li>如何(以及為什麼)避免非必要的初始化和循環相依(cyclic dependencies)?</li>
<li>何時(以及如何)應該同時使用靜態和動態多型(static and dynamic polymorphism)?</li>
<li>如何實踐安全的覆寫(“safe” overriding)?</li>
<li>何時應該提供一個 no-fail swap?</li>
<li>為什麼應該(以及如何)阻止異常跨模塊邊界(across module boundaries)傳播?</li>
<li>為什麼不該在表頭檔內寫 namespace 的宣告式或指令(declarations or directives)?</li>
<li>為什麼應該使用 STL 的 vector 和 string 來取代 arrays?</li>
<li>如何選擇正確的 STL search 或 sort 演算法?</li>
<li>應該遵循什麼規則來保證 type-safe 程式碼?</li>
</ul>
<p>不論個人或團隊,本書助你寫出更乾淨的程式碼 — 而且更快寫出,帶著更少的困難和挫敗。</p>
<hr>
<h2 id="unix-網絡編程">#28 UNIX 網絡編程</h2>
<blockquote>
<p>(僅有簡體版)<br>
原文書名:Unix Network Programming, Vol. 1: The Sockets Networking API<br>
作者:W. Richard Stevens, Bill Fenner, Andrew M. Rudoff<br>
<a href="https://www.tenlong.com.tw/products/9787115367198">天瓏連結(簡體版)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/085/974/medium/51pNBR2prIL.jpg?1525645456" alt="UNIX 網絡編程"></p>
<p>史蒂文斯、芬納、魯道夫編著的《UNIX 網絡編程(捲 1 套接字聯網 API 第 3 版)》是一部 UNIX 網絡編程的經典之作!書中全面深入地介紹瞭如何使用套接字 API 進行網絡編程。全書不但介紹了基本編程內容,還涵蓋了與套接字編程相關的高級主題,對於客戶 ∕ 服務器程序的各種設計方法也作了完整的探討,最後還深入分析了流這種設備驅動機制。<br>
本書內容詳盡且具權威性,幾乎每章都提供精選的習題,並提供了部分習題的答案,是網絡研究和開發人員理想的參考書。</p>
<hr>
<h2 id="purely-functional-data-structures">#29 Purely Functional Data Structures</h2>
<blockquote>
<p>(無中文版)<br>
作者:Okasaki<br>
<a href="https://www.tenlong.com.tw/products/9780521663502">天瓏連結(原文書)</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/003/637/medium/41XlPaC_ZqL.jpg?1525622803" alt="Purely Functional Data Structures"></p>
<p>Most books on data structures assume an imperative language such as C or C++. However, data structures for these languages do not always translate well to functional languages such as Standard ML, Haskell, or Scheme. This book describes data structures from the point of view of functional languages, with examples, and presents design techniques that allow programmers to develop their own functional data structures. The author includes both classical data structures, such as red-black trees and binomial queues, and a host of new data structures developed exclusively for functional languages. All source code is given in Standard ML and Haskell, and most of the programs are easily adaptable to other functional languages. This handy reference for professional programmers working with functional languages can also be used as a tutorial or for self-study.</p>
<hr>
<h2 id="單元測試的藝術">#30 單元測試的藝術</h2>
<blockquote>
<p>原文書名:The Art of Unit Testing: with examples in C#<br>
作者:Roy Osherove<br>
<a href="https://www.tenlong.com.tw/products/9789864342471">天瓏連結</a></p>
</blockquote>
<p><img src="https://cf-assets2.tenlong.com.tw/products/images/000/110/283/medium/MP11802_BIG.jpg?1525539971" alt="單元測試的藝術"></p>
<p>理解如何撰寫單元測試,以及讓它們變得可維護、可讀、可被信任,是這本書的主要內容,不管你使用的是何種程式語言跟編輯器。這本書涵蓋了撰寫單元測試的基本知識,並且講解互動測試的基礎,介紹了在真實世界撰寫、管理和維護單元測試的最佳實踐。」</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>參考資料:<a href="http://www.dev-books.com">Top mentioned books on stackoverflow.com</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
<br />
<br />
<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license"><img alt="創用 CC 授權條款" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" style="border-width: 0;" /></a><br />
本著作由<a href="https://goodjack.blogspot.com/" property="cc:attributionName" rel="cc:attributionURL" xmlns:cc="http://creativecommons.org/ns#">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license">創用CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-29477688073487264782019-06-03T23:54:00.001+08:002019-06-03T23:54:15.668+08:00小克的 Visual Studio Code 必裝擴充套件(Extensions)私藏推薦<p> 沒錯,又是私藏推薦系列,不過這次是 Visual Studio Code。<br>
(上一篇:<a href="https://goodjack.blogspot.com/2017/10/2017-2018-ios-android-app.html">小克 2017 不能沒有、2018 一定繼續用的必裝 app 下載私藏推薦 (Android/iOS)</a>)</p>
<p>從 Sublime Text 3 跳到 VS Code 一陣子了,在微軟及社群頻繁的維護與更新下,總算是越用越順手。<strong>VS Code 擴充功能</strong>(Extensions,俗稱<strong>擴充套件</strong>)的生態也算是越來越完整,一點也不輸給 Sublime Text 或是 Atom 的外掛或擴充套件。</p>
<p>算一算裝的擴充功能也有數十種了,到了要整理的地步(笑),那就寫一篇來記錄一下好了,這篇也會持續更新。我平時主力為 Laravel/PHP 開發,所以會有比較多 Laravel 和 PHP 的相關套件。</p>
<p>所以以下就是我的私人珍藏、必裝、必備、必下載、不裝不行的 VS Code 擴充功能。(直接拿 app 那篇來改 XDDD)<br>
如果你有喜歡的其他擴充功能沒列到,也歡迎留言告訴我(各種方面的都歡迎建議)</p>
<blockquote>
<p><img src="https://code.visualstudio.com/opengraphimg/opengraph-home.png" alt=""><br>
來放一個官方的圖給 Facebook 抓預覽</p>
</blockquote>
<hr>
<h2 id="目錄----omit-in-toc---">目錄 <!-- omit in toc --></h2>
<ul>
<li><a href="#changelog">Changelog</a></li>
<li><a href="#%E6%95%88%E7%8E%87%E6%8F%90%E5%8D%87%E5%BC%B7%E7%83%88%E6%8E%A8%E8%96%A6%E9%A1%9E">效率提升強烈推薦類</a></li>
<li><a href="#git-%E7%9B%B8%E9%97%9C%E5%BF%85%E5%82%99%E5%A4%96%E6%8E%9B">Git 相關必備外掛</a></li>
<li><a href="#sublime-text-%E6%83%85%E6%87%B7%E5%BF%85%E8%A3%9D">Sublime Text 情懷必裝</a></li>
<li><a href="#%E9%80%9A%E7%94%A8%E5%8A%A0%E5%88%86%E5%B7%A5%E5%85%B7">通用加分工具</a></li>
<li><a href="#markdown-%E7%9B%B8%E9%97%9C">Markdown 相關</a></li>
<li><a href="#swagger-%E6%96%87%E4%BB%B6%E7%9B%B8%E9%97%9C">Swagger 文件相關</a></li>
<li><a href="#javascript--vue">JavaScript / Vue</a></li>
<li><a href="#laravel-%E7%9B%B8%E9%97%9C%E5%BF%85%E8%BC%89%E5%B7%A5%E5%85%B7">Laravel 相關必載工具</a></li>
<li><a href="#php-%E7%9B%B8%E9%97%9C%E6%8E%A8%E8%96%A6">PHP 相關推薦</a></li>
<li><a href="#python-%E5%B0%88%E5%8D%80">Python 專區</a></li>
<li><a href="#%E5%A4%96%E8%A7%80%E5%80%8B%E4%BA%BA%E5%96%9C%E5%A5%BD%E5%8D%80">外觀個人喜好區</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E8%80%85%E8%A8%AD%E5%AE%9A">使用者設定</a></li>
</ul>
<hr>
<h2 id="changelog">Changelog</h2>
<ul>
<li>2019.06.03 新增以下工具
<ul>
<li>Markdown 相關工具</li>
<li>Swagger 文件相關工具</li>
<li>Todo Tree</li>
<li>Prettier</li>
<li>Docker</li>
<li>Laravel goto view、Laravel-goto-controller</li>
</ul>
</li>
<li>2019.06.03 VSCode PHPUnit 更名為 PHPUnit Test Explorer</li>
<li>2018.08.03 新增 PHPUnit Snippets</li>
<li>2018.08.03 將 Better PHPUnit 替換成更好用的 VSCode PHPUnit</li>
<li>2018.04.02 裝回 Git History</li>
<li>2018.03.27 使用者設定中新增 emmet 和 trimTrailingWhitespace 設定</li>
<li>2018.03.24 新增使用者設定</li>
<li>2018.03.24 新增 Fira Code</li>
<li>2018.03.24 初版</li>
</ul>
<hr>
<h2 id="效率提升強烈推薦類">效率提升強烈推薦類</h2>
<p>這個區塊表列的,應該是不裝我會覺得渾身不對勁的的擴充功能 😂</p>
<ul>
<li>
<p><a href="https://github.com/tonsky/FiraCode">Fira Code</a>(<a href="https://github.com/tonsky/FiraCode/wiki/VS-Code-Instructions">安裝方法在此</a>)<br>
首先推薦的這個不是套件,而是字體,強烈建議安裝,爽度提昇超多!<br>
<img src="https://github.com/tonsky/FiraCode/raw/master/showcases/swift.png" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync">Settings Sync</a><br>
如果你有多台電腦,可以幫你同步設定檔和安裝的套件(透過 GitHub Gist),支援背景自動同步<br>
<img src="https://media.giphy.com/media/xT9IglKxSqs2Wdwq2c/source.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-close-tag">Auto Close Tag</a><br>
被 Sublime Text 慣壞的必裝,會幫你把右括號或結束標籤補上。現在 VS Code 有內建基本補完功能了,但是他支援更多符號和設定,例如我喜歡打 <code></</code> 的時候也幫我補上 HTML close tag。<br>
<img src="https://github.com/formulahendry/vscode-auto-close-tag/raw/master/images/st3.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=spywhere.guides">Guides</a><br>
這工具我找超久!在你的程式開關符號(例如左右大括號)拉一條線,方便你識別程式區塊<br>
<img src="https://github.com/spywhere/vscode-guides/raw/master/images/screenshot.png" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight">TODO Highlight</a><br>
Highlight 你的 <code>TODO</code>、<code>FIXME</code> 註解,還可以幫你統整整個專案的 TODOs<br>
<img src="https://github.com/wayou/vscode-todo-highlight/raw/master/assets/list-annotations.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight">Todo Tree</a><br>
以樹狀目錄方式統整整個專案的 TODOs<br>
<img src="https://raw.githubusercontent.com/Gruntfuggly/todo-tree/master/resources/screenshot.png" alt=""></p>
</li>
</ul>
<hr>
<h2 id="git-相關必備外掛">Git 相關必備外掛</h2>
<p>輔助使用,還是喜歡直接使用 GitKraken、SourceTree 或直接 Terminal 操作。</p>
<ul>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory">Git History</a><br>
原本把這個套件移除了,結果又裝回來。這個套件的主要功能,就是可以方便查看 Git 紀錄以及檔案歷史,甚至是 Line History!並可直接連結到變更的檔案。介面比下面的 GitLens 方便得多。<br>
<img src="https://raw.githubusercontent.com/DonJayamanne/gitHistoryVSCode/master/images/lineHistoryCommand.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens">GitLens — Git supercharged</a><br>
必備的全能 Git 擴充功能。我最喜歡的功能是閃爍游標指到哪一行他就會顯示該行的 Git Blame,可以快速查看是哪個 commit 修改的。他竟然有自己的設定頁面,其他擴充功能快跟上好嗎!<br>
噢對了,它的缺點是會塞很多功能在右鍵,不喜歡的話記得去設定調整。<br>
<img src="https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/docs/gitlens-preview.gif" alt=""></p>
</li>
</ul>
<hr>
<h2 id="sublime-text-情懷必裝">Sublime Text 情懷必裝</h2>
<p>從 Sublime Text 搬過來嗎?</p>
<ul>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=Zarel.sublime-commands">Sublime Commands</a><br>
把一些 Sublime Text 好用的游標功能搬過來 VS Code,例如 Transpose、選取整行、行列合併、快速指定多行游標等</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.sublime-keybindings">Sublime Text Keymap</a><br>
把 Sublime Text 的快速鍵對應到 VS Code 的相對功能上</p>
</li>
</ul>
<hr>
<h2 id="通用加分工具">通用加分工具</h2>
<p>這裡應該是寫什麼語言都適用的好用工具</p>
<ul>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig">EditorConfig for VS Code</a><br>
統一專案中檔案的編輯風格,這個就沒有圖片示例了,可以查一下 <a href="http://editorconfig.org/">EditorConfig</a> 的功能。</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=silverlakesoftware.searchdocsets-vscode">Search Docsets</a><br>
查詢文件的輔助工具,支援 Dash、Velocity、Zeal</p>
</li>
<li>
<p><a href="https:/marketplace.visualstudio.com/items?itemName=patbenatar.advanced-new-file">advanced-new-file</a><br>
我裝的是 patbenatar 做的版本,可以很快速的在指定目錄建立新的檔案<br>
<img src="https://media.giphy.com/media/l3vRfRJO7ZX6WNJQs/source.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv">DotENV</a><br>
讓 .env 檔也有 syntax 效果<br>
<img src="https://github.com/mikestead/vscode-dotenv/raw/master/images/screenshot.png" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=vittorioromeo.expand-selection-to-scope">Expand Selection To Scope</a><br>
可以外擴滑鼠點兩下的選取範圍<br>
<img src="https://github.com/SuperV1234/vscode-expand-selection-to-scope/raw/master/example.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=sleistner.vscode-fileutils">File Utils</a><br>
方便檔案處理使用,複製、移動、改名、刪檔<br>
<img src="https://github.com/sleistner/vscode-fileutils/raw/master/images/demo.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=PeterJausovec.vscode-docker">Docker</a><br>
方便管理及操作 Docker Image、Container 的官方工具<br>
<img src="https://github.com/microsoft/vscode-docker/raw/master/images/explorer.png" alt=""></p>
</li>
</ul>
<hr>
<h2 id="markdown-相關">Markdown 相關</h2>
<ul>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one">Markdown All in One</a><br>
一應俱全的 Markdown 工具,無論是 highlight、快速鍵、自動補完、預覽,通通給你!<br>
<img src="https://github.com/yzhang-gh/vscode-markdown/raw/master/images/gifs/multi-ctrl-b-light.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint">markdownlint</a><br>
Markdown 格式美化的參考標準</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=bierner.markdown-preview-github-styles">Markdown Preview Github Styling</a><br>
將 VSCode 內建的 Markdown 樣式替換成更接近 GitHub 的外觀<br>
<img src="https://github.com/mjbvz/vscode-github-markdown-preview-style/raw/master/docs/example.png" alt=""></p>
</li>
</ul>
<hr>
<h2 id="swagger-文件相關">Swagger 文件相關</h2>
<ul>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=Arjun.swagger-viewer">Swagger Viewer</a><br>
在撰寫 Swagger OpenAPI 文件時可以方便地預覽<br>
<img src="https://cdn.rawgit.com/arjun-g/vs-swagger-viewer/master/docs/swagger-preview.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml">YAML</a><br>
讓 VSCode 支援 YAML 格式<br>
<img src="https://raw.githubusercontent.com/redhat-developer/vscode-yaml/master/images/demo.gif" alt=""></p>
</li>
</ul>
<hr>
<h2 id="javascript--vue">JavaScript / Vue</h2>
<p>身為工程師,總是要碰一點 JS。</p>
<ul>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=xabikos.JavaScriptSnippets">JavaScript (ES6) code snippets</a><br>
不用多說,就是 JS 的 Snippets。也同時支援 TypeScript、React、Vue</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">Prettier - Code formatter</a><br>
紅極一時的 JS 格式化工具,統一 JS 程式碼風格</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=octref.vetur">Vetur</a><br>
Vue 的集大成工具,寫 Vue 必裝</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=hollowtree.vue-snippets">Vue 2 Snippets</a><br>
Vue 2 的程式碼片段</p>
</li>
</ul>
<hr>
<h2 id="laravel-相關必載工具">Laravel 相關必載工具</h2>
<p>Laravel 是目前 GitHub 開源專案中最多 Star 的後端框架哦!比 Rails、Django、Express 都還高。</p>
<ul>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=onecentlin.laravel5-snippets">Laravel 5 Snippets</a><br>
台灣社群 Winnie Lin 的版本。讓 VS Code 支援 Laravel 程式碼片段<br>
<img src="https://github.com/onecentlin/laravel5-snippets-vscode/raw/master/images/screenshot.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=ryannaddy.laravel-artisan">Laravel Artisan</a><br>
在 VS Code 直接執行 <code>php artisan</code> 的功能,更棒的是可以直接看 route list!<br>
<img src="https://github.com/TheColorRed/vscode-laravel-artisan/raw/master/images/screens/route-list.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=onecentlin.laravel-blade" title="https://marketplace.visualstudio.com/items?itemName=onecentlin.laravel-blade">Laravel Blade Snippets</a><br>
一樣是台灣社群 Winnie Lin 的版本。這次是讓 VS Code 支援 Laravel Blade 的程式碼片段<br>
<img src="https://github.com/onecentlin/laravel-blade-snippets-vscode/raw/master/images/screenshot.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=codingyu.laravel-goto-view">Laravel goto view</a><br>
方便在程式碼中直接跳轉開啟對應的 Blade 檔案<br>
<img src="https://github.com/codingyu/laravel-goto-view/raw/master/images/use.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=stef-k.laravel-goto-controller">Laravel-goto-controller</a><br>
方便在 Route 中直接跳轉開啟對應的 Controller 檔案<br>
<img src="https://github.com/stef-k/laravel-goto-controller/raw/master/images/laravel-goto-controller.gif" alt=""></p>
</li>
</ul>
<hr>
<h2 id="php-相關推薦">PHP 相關推薦</h2>
<p>PHP 7 強勢再起,不要再鄙視他了 QQ</p>
<ul>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=junstyle.php-cs-fixer">php cs fixer</a><br>
搭配 <a href="https://github.com/FriendsOfPHP/PHP-CS-Fixer">PHP CS Fixer</a> 使用,自動在儲存時將程式碼修正成符合 PSR-2 的規範格式。重視 Coding Style 者必裝!</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=ikappas.phpcs">phpcs</a><br>
讓 VSCode 支援 <a href="https://github.com/squizlabs/PHP_CodeSniffer">PHP CodeSniffer</a>,幫你檢查程式碼不符合規範的地方。一樣是重視 Coding Style 者必裝的套件!</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=neilbrayfield.php-docblocker">PHP DocBlocker</a><br>
協助撰寫 PHP DocBlock,會幫你自動補完 DocBlock tags。<br>
關於什麼是 PHP DocBlock 可以參考這篇:<a href="http://oomusou.io/phpstorm/phpstorm-phpdoc/">如何使用 PHPDoc 寫註解? | 點燈坊</a></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client">PHP Intelephense</a><br>
就是個 PHP intellisense,我是裝 Ben Mewburn 做的版本。</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=recca0120.vscode-phpunit">PHPUnit Test Explorer</a><br>
寫測試的好用工具!原先使用 <a href="https://marketplace.visualstudio.com/items?itemName=onecentlin.phpunit-snippets">PHPUnit Snippets</a>,現在換成這個台灣社群 Recca Tsai 製作的版本,更直覺簡單使用。<br>
<img src="https://raw.githubusercontent.com/recca0120/vscode-phpunit/master/img/screenshot.png" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=onecentlin.phpunit-snippets">PHPUnit Snippets</a><br>
台灣社群 Winnie Lin 製作的版本,自動完成測試時所需的程式碼片段。<br>
<img src="https://github.com/onecentlin/phpunit-snippets-vscode/raw/master/images/screenshots.gif" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug">PHP Debug</a><br>
搭配 <a href="https://xdebug.org/docs/install">XDebug</a> 使用,可以在 VS Code 中除錯、下中斷點。<br>
<img src="https://github.com/felixfbecker/vscode-php-debug/raw/master/images/demo.gif" alt=""></p>
</li>
</ul>
<hr>
<h2 id="python-專區">Python 專區</h2>
<p>簡單好用又強大的語言。</p>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python">Python</a><br>
就是 Python 的集大成擴充功能,已經由微軟官方親自維護</li>
</ul>
<hr>
<h2 id="外觀個人喜好區">外觀個人喜好區</h2>
<p>我就只是個 Material Design 粉。</p>
<ul>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=PKief.material-icon-theme">Material Icon Theme</a><br>
好的圖示主題是一定要裝的<br>
<img src="https://raw.githubusercontent.com/PKief/vscode-material-icon-theme/master/images/fileIcons.png" alt=""></p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=zhuangtongfa.Material-theme">One Dark Pro</a><br>
好的色彩主題也是一定要裝的<br>
<img src="https://raw.githubusercontent.com/Binaryify/OneDark-Pro/master/static/js.png" alt=""></p>
</li>
</ul>
<hr>
<h2 id="使用者設定">使用者設定</h2>
<p>最後附上我的 settings.json 提供參考</p>
<pre class=" language-json"><code class="prism language-json"><span class="token punctuation">{</span>
<span class="token string">"auto-close-tag.fullMode"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"blade.format.enable"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"diffEditor.ignoreTrimWhitespace"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"editor.fontFamily"</span><span class="token punctuation">:</span> <span class="token string">"Fira Code"</span><span class="token punctuation">,</span>
<span class="token string">"editor.fontLigatures"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"editor.fontSize"</span><span class="token punctuation">:</span> <span class="token number">18</span><span class="token punctuation">,</span>
<span class="token string">"editor.formatOnPaste"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"editor.lineHeight"</span><span class="token punctuation">:</span> <span class="token number">32</span><span class="token punctuation">,</span>
<span class="token string">"editor.renderIndentGuides"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"editor.renderWhitespace"</span><span class="token punctuation">:</span> <span class="token string">"boundary"</span><span class="token punctuation">,</span>
<span class="token string">"editor.scrollBeyondLastLine"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"editor.snippetSuggestions"</span><span class="token punctuation">:</span> <span class="token string">"top"</span><span class="token punctuation">,</span>
<span class="token string">"emmet.includeLanguages"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
<span class="token string">"blade"</span><span class="token punctuation">:</span> <span class="token string">"html"</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">"emmet.triggerExpansionOnTab"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"files.trimTrailingWhitespace"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"gitlens.advanced.menus"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
<span class="token string">"editorContext"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
<span class="token string">"blame"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"copy"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"details"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"fileDiff"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"history"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"lineDiff"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"remote"</span><span class="token punctuation">:</span> <span class="token boolean">false</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">"explorerContext"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
<span class="token string">"fileDiff"</span><span class="token punctuation">:</span> <span class="token boolean">false</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">"gitlens.advanced.messages"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
<span class="token string">"suppressResultsExplorerNotice"</span><span class="token punctuation">:</span> <span class="token boolean">true</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">"gitlens.blame.heatmap.location"</span><span class="token punctuation">:</span> <span class="token string">"left"</span><span class="token punctuation">,</span>
<span class="token string">"gitlens.codeLens.scopes"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"document"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token string">"gitlens.gitExplorer.files.compact"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"gitlens.hovers.currentLine.details"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"gitlens.statusBar.command"</span><span class="token punctuation">:</span> <span class="token string">"gitlens.showQuickFileHistory"</span><span class="token punctuation">,</span>
<span class="token string">"guides.active.color.dark"</span><span class="token punctuation">:</span> <span class="token string">"rgba(120, 120, 120, 0.75)"</span><span class="token punctuation">,</span>
<span class="token string">"guides.normal.style"</span><span class="token punctuation">:</span> <span class="token string">"none"</span><span class="token punctuation">,</span>
<span class="token string">"guides.stack.enabled"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"php.suggest.basic"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"php.validate.run"</span><span class="token punctuation">:</span> <span class="token string">"one"</span><span class="token punctuation">,</span>
<span class="token string">"php-cs-fixer.onsave"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"python.formatting.provider"</span><span class="token punctuation">:</span> <span class="token string">"yapf"</span><span class="token punctuation">,</span>
<span class="token comment">// "python.venvPath": "~/.pyenv",</span>
<span class="token string">"sublimeTextKeymap.promptV3Features"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"sync.askGistName"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"sync.autoDownload"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"sync.autoUpload"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"sync.forceDownload"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token string">"sync.gist"</span><span class="token punctuation">:</span> <span class="token string">"你的 GIST 位置"</span><span class="token punctuation">,</span>
<span class="token string">"sync.host"</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
<span class="token string">"sync.lastDownload"</span><span class="token punctuation">:</span> <span class="token string">"2018-03-22T02:34:44.923Z"</span><span class="token punctuation">,</span>
<span class="token string">"sync.lastUpload"</span><span class="token punctuation">:</span> <span class="token string">"2018-03-24T10:12:21.567Z"</span><span class="token punctuation">,</span>
<span class="token string">"sync.pathPrefix"</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span>
<span class="token string">"sync.quietSync"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"sync.removeExtensions"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"sync.syncExtensions"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"terminal.external.osxExec"</span><span class="token punctuation">:</span> <span class="token string">"iTerm.app"</span><span class="token punctuation">,</span>
<span class="token string">"terminal.integrated.fontFamily"</span><span class="token punctuation">:</span> <span class="token string">"Roboto Mono for Powerline"</span><span class="token punctuation">,</span>
<span class="token string">"terminal.integrated.fontSize"</span><span class="token punctuation">:</span> <span class="token number">17</span><span class="token punctuation">,</span>
<span class="token string">"terminal.integrated.lineHeight"</span><span class="token punctuation">:</span> <span class="token number">1.5</span><span class="token punctuation">,</span>
<span class="token string">"window.openFilesInNewWindow"</span><span class="token punctuation">:</span> <span class="token string">"on"</span><span class="token punctuation">,</span>
<span class="token string">"window.zoomLevel"</span><span class="token punctuation">:</span> <span class="token operator">-</span><span class="token number">0.5</span><span class="token punctuation">,</span>
<span class="token string">"workbench.activityBar.visible"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token string">"workbench.colorTheme"</span><span class="token punctuation">:</span> <span class="token string">"One Dark Pro"</span><span class="token punctuation">,</span>
<span class="token string">"workbench.iconTheme"</span><span class="token punctuation">:</span> <span class="token string">"material-icon-theme"</span>
<span class="token punctuation">}</span>
</code></pre>
<p>本著作由<a href="http://goodjack.blogspot.tw/">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/">創用 CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。<br>
本篇永久網址:<a href="http://goodjack.blogspot.com/2018/03/visual-studio-code-extensions.html">http://goodjack.blogspot.com/2018/03/visual-studio-code-extensions.html</a></p>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-19700654822628518452019-03-28T17:35:00.001+08:002019-03-28T17:35:47.681+08:00[筆記] 因為 Debian Jessie 停止支援造成 Dockerfile 執行 apt update 出錯<p>這幾天使用舊版 Laradock 的時候,在 build 到 laradock/php-fpm 相關的 Dockerfile 後發生問題了。</p>
<p><img src="https://images.pexels.com/photos/207580/pexels-photo-207580.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260" alt="enter image description here"><br>
<em>隨便放一個圖當社群網站的預覽圖</em></p>
<h2 id="錯誤訊息">錯誤訊息</h2>
<p>當 Dockerfile 嘗試執行 apt update(或 apt-get update)時出現以下錯誤</p>
<blockquote>
<p>W: Failed to fetch <a href="http://deb.debian.org/debian/dists/jessie-updates/main/binary-amd64/Packages">http://deb.debian.org/debian/dists/jessie-updates/main/binary-amd64/Packages</a> 404 Not Found<br>
<br>
E: Some index files failed to download. They have been ignored, or old ones used instead.</p>
</blockquote>
<h2 id="原因:jessie-ppa-已封存">原因:Jessie PPA 已封存</h2>
<p>Debian 8(代號 Jessie)已經停止維護,官方套件庫(PPA)<code>jessie</code> 已經移至 <a href="http://archive.debian.org">archive.debian.org</a> 封存,並且廢止 <code>jessie-updates</code> 套件庫(合併至<code>jessie</code> )。</p>
<p><code>jessie</code> 會自動從 <a href="http://deb.debian.org">deb.debian.org</a> 轉址到 <a href="http://archive.debian.org">archive.debian.org</a>。但是由於 <code>jessie-updates</code> 已經廢除,產生 404 錯誤,連帶影響到 Docker 建置失敗。</p>
<p>以下是相關的資訊:<br>
<a href="https://lists.debian.org/debian-devel-announce/2019/03/msg00006.html">Removal of Wheezy and Jessie (except LTS) from mirrors</a></p>
<blockquote>
<p>as Wheezy and Jessie have been integrated into the <a href="http://archive.debian.org">archive.debian.org</a> structure recently, we are now removing all of Wheezy and all non-LTS architectures of Jessie from the mirror network starting today.</p>
</blockquote>
<h2 id="解決方案">解決方案</h2>
<p>將已廢止的 <code>jessie-updates</code> 從 sources.list 中移除:</p>
<pre class=" language-dockerfile"><code class="prism language-dockerfile"><span class="token keyword">RUN</span> sed <span class="token punctuation">-</span>i <span class="token string">'/jessie-updates/d'</span> /etc/apt/sources.list <span class="token comment"># Now archived</span>
</code></pre>
<p>原本的 /etc/apt/sources.list 內容如果是長這樣:</p>
<pre class=" language-bash"><code class="prism language-bash">deb http://deb.debian.org/debian jessie main
deb http://deb.debian.org/debian jessie-updates main
deb http://security.debian.org jessie/updates main
</code></pre>
<p>執行以上指令後就會變成:</p>
<pre class=" language-bash"><code class="prism language-bash">deb http://deb.debian.org/debian jessie main
deb http://security.debian.org jessie/updates main
</code></pre>
<h2 id="murmur">Murmur</h2>
<p>還是盡快升級比較好,很怕留下更多技術債啊!</p>
<h2 id="參考資料">參考資料</h2>
<ul>
<li><a href="https://github.com/debuerreotype/docker-debian-artifacts/issues/66#issuecomment-476616579">Jessie has been archived; sources.list should be updated · Issue #66 · debuerreotype/docker-debian-artifacts</a></li>
</ul>
<br />
<br />
<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license"><img alt="創用 CC 授權條款" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" style="border-width: 0;" /></a><br />
本著作由<a href="https://goodjack.blogspot.com/" property="cc:attributionName" rel="cc:attributionURL" xmlns:cc="http://creativecommons.org/ns#">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license">創用CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-65761937383748100922019-03-04T16:05:00.001+08:002019-03-04T16:05:06.709+08:00[筆記] 設定了 POWERLEVEL9K_MODE 卻沒顯示圖示?<p>裝了 <a href="http://ohmyz.sh/">Oh My Zsh</a> 後,應該很自然地都會想要裝上 <a href="https://github.com/bhilburn/powerlevel9k">Powerlevel9k</a> 吧(咦?<br>
然後也會很自然地想要把圖示通通都打開對吧(咦咦?</p>
<p>這時候就會發現,奇怪我都已經照官方教學把 <code>POWERLEVEL9K_MODE</code> 設定的妥妥了(順便曬一下我陽春的 Powerlevel9k 設定):</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token comment"># ~/.zshrc</span>
POWERLEVEL9K_MODE<span class="token operator">=</span><span class="token string">'nerdfont-complete'</span>
POWERLEVEL9K_LEFT_PROMPT_ELEMENTS<span class="token operator">=</span><span class="token punctuation">(</span>os_icon context virtualenv <span class="token function">dir</span> vcs<span class="token punctuation">)</span>
POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS<span class="token operator">=</span><span class="token punctuation">(</span>status root_indicator background_jobs time<span class="token punctuation">)</span>
POWERLEVEL9K_SHORTEN_DIR_LENGTH<span class="token operator">=</span>2
POWERLEVEL9K_CONTEXT_TEMPLATE<span class="token operator">=</span><span class="token string">"%n"</span>
POWERLEVEL9K_PROMPT_ON_NEWLINE<span class="token operator">=</span>true
POWERLEVEL9K_RPROMPT_ON_NEWLINE<span class="token operator">=</span>false
</code></pre>
<p>怎麼還是沒有圖勒<br>
<img src="https://i.imgur.com/p0kLY5Kl.jpg" alt="圖勒"></p>
<p>我意思是,像下面官方示範的各種精美圖示,怎麼還是沒有出來勒?<br>
<img src="http://i.imgur.com/hviMATC.png" alt="enter image description here"></p>
<p>原來是 <a href="https://github.com/bhilburn/powerlevel9k/wiki/Install-Instructions">官方 Wiki 安裝說明</a> 針對 Nerd-Fonts 的段落忘記強調一句話,而在 Awesome-Powerline Fonts 段落有提及:</p>
<blockquote>
<p>You then need to indicate that you wish to use the additional glyphs by defining <strong>one</strong> of the following in your <code>~/.zshrc</code> <strong>before you specify the powerlevel9k theme</strong></p>
</blockquote>
<p>意思是,<strong><code>POWERLEVEL9K_MODE</code>必須要比 <code>ZSH_THEME</code> 還前面</strong>啦!</p>
<p>以我的例子為例,只要把</p>
<pre class=" language-bash"><code class="prism language-bash">POWERLEVEL9K_MODE<span class="token operator">=</span><span class="token string">'nerdfont-complete'</span>
</code></pre>
<p>這一行往前搬,像這樣:</p>
<pre class=" language-bash"><code class="prism language-bash">POWERLEVEL9K_MODE<span class="token operator">=</span><span class="token string">'nerdfont-complete'</span>
ZSH_THEME<span class="token operator">=</span><span class="token string">"powerlevel9k/powerlevel9k"</span>
</code></pre>
<p>現在圖示就正常顯示啦!</p>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-38178299800599014052019-03-04T08:46:00.003+08:002019-03-04T08:46:53.445+08:00如何解決 macOS 安裝 Boot Camp 失敗<p>試著用 Parallels Desktop 來「還願」,結果所有原本應該暗到看不見的地方都出現了紫色濾鏡效果,該有反射的材質也都沒出現反射,整個壞了興致(意外的是用內顯跑其實蠻順的)。</p>
<p><img src="https://i.imgur.com/CwBoTzd.jpg" alt=""></p>
<p>因此決定還是來裝個 Boot Camp(啟動切換)玩玩看。</p>
<p>原本以為只要開啟「啟動切換輔助程式」,順順的按下一步就好。結果安裝超級不順利!</p>
<h2 id="tldr">TL;DR</h2>
<ol>
<li>移除 <code>OSXRESERVED</code>、<code>BOOTCAMP</code> 兩個卷宗。</li>
<li>改安裝 Windows 10 2018 <strong>April</strong> Update,而不是 <strong>Octobar</strong> Update。</li>
</ol>
<h2 id="先說出問題的環境">先說出問題的環境</h2>
<p><img src="https://i.imgur.com/GMVX6zH.png" alt=""></p>
<ul>
<li>Macbook Pro 2018</li>
<li>macOS 10.14.3</li>
<li>Windows 10 光碟映像 (ISO 檔案) (2018 October Update)</li>
</ul>
<h2 id="安裝遭遇錯誤">安裝遭遇錯誤</h2>
<p>結果竟然在安裝的時候失敗了!(裝三次結果都一樣)</p>
<blockquote>
<p><strong>無法分割硬碟</strong><br>
分割磁碟時發生問題。請執行「磁碟工具程式」來檢查並修正錯誤。</p>
</blockquote>
<p><img src="https://i.imgur.com/HXuprox.png" alt=""></p>
<p>這裡我已經 <s>幫大家</s> 重試三次檢查磁區,甚至我還進去「macOS 回復」來修復過磁碟也是一樣。</p>
<h2 id="重新執行呢?">重新執行呢?</h2>
<p>當我們想說,失敗了那我重新安裝總可以吧?好的,就會遇到以下錯誤訊息:</p>
<blockquote>
<p><strong>無法分割啟動磁碟或將磁碟回復為單一分割區。</strong><br>
啟動磁碟必須格式化為單一 Mac OS 擴充格式(日誌式)卷宗或用「啟動切換輔助程式」分割磁碟後才能安裝 Windows。</p>
</blockquote>
<p><img src="https://i.imgur.com/yFBBVfi.png" alt=""></p>
<h2 id="接下來該怎麼做?">接下來該怎麼做?</h2>
<p>好的,接著我們來試著刪除 Boot Camp 磁區。打開「磁碟工具程式」,看看現在磁區長怎樣?<br>
<img src="https://i.imgur.com/YzYh8mp.png" alt=""><br>
這裡看到了多出來 <code>OSXRESERVED</code>、<code>BOOTCAMP</code> 兩個卷宗,以及一個磁碟映像檔(應該是掛載的 Windows 映像檔)。</p>
<p>這裡我卡很久,我以為 <code>OSXRESERVED</code> 是 macOS 的還原磁區不敢刪除,但是 <code>BOOTCAMP</code> 卷宗刪除後,因為中間插了個 <code>OSXRESERVED</code> 卷宗,就無法合併回「啟動切換輔助程式」所說的「單一分割區」。</p>
<p>意思是:我也無法移除 Boot Camp。</p>
<p>後來陰錯陽差找到了 Apple 的說明文件:<a href="https://support.apple.com/zh-tw/HT209351">如果「啟動切換」輔助程式停止回應或指出「『啟動切換』安裝失敗」</a>,引用內文如下:</p>
<blockquote>
<p>當您在裝有之前版本 macOS 的 Mac 上使用 2018 年 10 月版的 Windows 10 執行全新安裝時,會出現「『啟動切換』安裝失敗」錯誤。</p>
</blockquote>
<p>有趣的是,我目前已經是 macOS 的最新版,但文件寫說:</p>
<blockquote>
<p>在安裝 Windows 前,請將 <a href="https://support.apple.com/zh-tw/HT201541">Mac 更新至最新的 macOS Mojave 版本</a> 即可避免上述錯誤。</p>
</blockquote>
<h2 id="移除分割區,然後重新安裝">移除分割區,然後重新安裝</h2>
<p>好吧,那我們先 <s>無視蘋果傷害更新控果粉的感情</s> 照著這個文件移除分割區看看吧。以下是官方建議的步驟:</p>
<ol>
<li>
<p>請前往 Microsoft <a href="https://www.microsoft.com/zh-tw/software-download/windows10ISO">下載 Windows 10 磁碟映像檔(ISO 檔)</a> 頁面。在「選擇版本」下的選單中,選取「Windows 10 April 2018 Update(2018 年 4 月的 Windows 10 更新)」。然後按照步驟下載 ISO 檔。</p>
</li>
<li>
<p>打開「磁碟工具程式」。</p>
</li>
<li>
<p>在「磁碟工具程式」選單列中,按一下「分割區」。如果看到對話框詢問是否想新增卷宗或分割區,請按一下「分割區」。</p>
</li>
<li>
<p>在出現的圖表中,選擇「OSXRESERVED」,然後按一下移除(減號)按鈕。</p>
</li>
<li>
<p>在同一圖表中,選擇「BOOTCAMP」,然後按一下移除(減號)按鈕。<br>
OSXRESERVED 和 BOOTCAMP 是「啟動切換」輔助程式在嘗試安裝期間所建立的空白分割區。</p>
</li>
<li>
<p>按一下「套用」,接著按一下「分割區」進行確認。</p>
</li>
<li>
<p>打開「啟動切換」輔助程式並再次嘗試安裝,務必使用「Windows 10 April 2018 Update(2018 年 4 月的 Windows 10 更新)」ISO 檔。</p>
</li>
</ol>
<p>官方還很貼心地告訴我們:</p>
<blockquote>
<p>完成安裝後,您可以使用 Windows Update 更新為 2018 年 10 月版本。</p>
</blockquote>
<p>裝完應該就可以正常使用 Boot Camp 了!</p>
<h2 id="murmur...">Murmur…</h2>
<p>2018 年 10 月的 Windows 版本,2019 年 3 月 macOS 還沒修好相容性…</p>
<br />
<br />
<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license"><img alt="創用 CC 授權條款" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" style="border-width: 0;" /></a><br />
本著作由<a href="https://goodjack.blogspot.com/" property="cc:attributionName" rel="cc:attributionURL" xmlns:cc="http://creativecommons.org/ns#">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license">創用CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-30150446838175767702019-02-11T16:55:00.001+08:002019-02-11T16:55:36.033+08:00[筆記] 設定 Sourcetree 預設使用分頁開啟多個 repo<p>Sourcetree 支援使用分頁功能,可以在一個視窗裡一次開啟多個 Git 倉儲(repository),讓畫面非常簡單乾淨。</p>
<p><img src="https://i.imgur.com/Tm6Yf7U.jpg" alt=""></p>
<p>但每次我都必須要開了好多個視窗後,才按 Window → Merge All Windows,未免也太麻煩了。</p>
<p>後來在 <a href="https://jira.atlassian.com/browse/SRCTREE-4524">[SRCTREE-4524] Add ‘Always Merge Windows’ option - Create and track feature requests for Atlassian products.</a> 這篇找到了解法。原來 Sourcetree 是使用 macOS 原生的<strong>標籤頁</strong>(Tab,我還是比較習慣說<strong>分頁</strong>)來實作這個功能。</p>
<p>因此解決方法是到 macOS 的「系統偏好設定」中的「Dock」,將「打開文件時偏好標籤頁」改成「總是」就可以了。</p>
<p><img src="https://i.imgur.com/1EAl2XX.png" alt=""></p>
<p>這樣做的缺點是,所有使用 macOS 標籤頁功能的程式都會連帶受到影響。不過我目前也沒看到哪個程式有用這個功能就是了 🤔</p>
<br />
<br />
<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license"><img alt="創用 CC 授權條款" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" style="border-width: 0;" /></a><br />
本著作由<a href="https://goodjack.blogspot.com/" property="cc:attributionName" rel="cc:attributionURL" xmlns:cc="http://creativecommons.org/ns#">小克</a>製作,以<a href="http://creativecommons.org/licenses/by-sa/4.0/" rel="license">創用CC 姓名標示-相同方式分享 4.0 國際 授權條款</a>釋出。
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-401450752931462832019-01-30T14:23:00.001+08:002019-01-30T14:23:55.131+08:00VSCode 與 PhpStrom 常用快速鍵對應表<p>最近開始試著把工作環境從 Visual Studio Code 轉移到 JetBrains 的 PhpStorm,看看 IDE 是不是真的有比編輯器方便撰寫 Laravel。</p>
<p><img src="http://resources.jetbrains.com/storage/products/phpstorm/img/meta/phpstorm_1280x800.png" alt="enter image description here"></p>
<p>在學習並適應使用 IDE 提供的強大功能前,原本編輯器就有提供的基本功能總要先瞭解該如何使用。很明顯的,遇到的一大難題大概就是一堆上手的快速鍵要重新適應了。是也可以考慮都換成 VSCode 的 keymap,但之前從 Sublime Text 轉換到 VSCode 的時候不也撐過來了,就來試試看記憶新的吧。</p>
<p>整理了一下常用的快速鍵,實在是變化太大了,能不能撐過去呢 🤔😂</p>
<h2 id="macos-符號圖例">macOS 符號圖例</h2>
<p>因為我用 macOS,這次就以 macOS 的快速鍵為主,以下是圖示對應的鍵盤按鍵。</p>
<table>
<thead>
<tr>
<th align="center">圖示</th>
<th align="center">鍵盤按鍵</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">⇧</td>
<td align="center">Shift</td>
</tr>
<tr>
<td align="center">⌃</td>
<td align="center">Control</td>
</tr>
<tr>
<td align="center">⌥</td>
<td align="center">Option</td>
</tr>
<tr>
<td align="center">⌘</td>
<td align="center">Command</td>
</tr>
</tbody>
</table><h2 id="我常用的快速鍵">我常用的快速鍵</h2>
<p>以下中文名稱是參照 VSCode 鍵盤快速鍵所做的翻譯,而英文則是 PhpStorm 提供的 keymap 名稱,大家可以對照著看。</p>
<table>
<thead>
<tr>
<th>名稱</th>
<th align="center">VSCode</th>
<th align="center">PhpStorm</th>
</tr>
</thead>
<tbody>
<tr>
<td>設定<br>Preferences…</td>
<td align="center">⌘,</td>
<td align="center">⌘,</td>
</tr>
<tr>
<td>快速開啟檔案<br>File…</td>
<td align="center">⌘P</td>
<td align="center">⇧⌘O</td>
</tr>
<tr>
<td>顯示所有命令<br>Find Action…</td>
<td align="center">⇧⌘P</td>
<td align="center">⇧⌘A</td>
</tr>
<tr>
<td>在檔案中尋找<br>Find in Path…</td>
<td align="center">⇧⌘F</td>
<td align="center">⇧⌘F</td>
</tr>
<tr>
<td>將選取項目加入下一個找到的相符項<br>Add Selection for Next Occurrence</td>
<td align="center">⌘D</td>
<td align="center">⌃G</td>
</tr>
<tr>
<td>選取所有找到的相符項目<br>Select All Occurrences</td>
<td align="center">⇧⌘L</td>
<td align="center">⌃⌘G</td>
</tr>
<tr>
<td>將行向下複製<br>Duplicate Line or Selection</td>
<td align="center">⇧⌥↓</td>
<td align="center">⌘D</td>
</tr>
<tr>
<td>上移一行<br>Move Line Up</td>
<td align="center">⌥↑</td>
<td align="center">⇧⌥↑</td>
</tr>
<tr>
<td>下移一行<br>Move Line Down</td>
<td align="center">⌥↓</td>
<td align="center">⇧⌥↓</td>
</tr>
<tr>
<td>移至定義<br>Declaration</td>
<td align="center">F12 <em>或</em><br>⌘<code>滑鼠左鍵</code></td>
<td align="center">⌘B <em>或</em><br>⌘<code>滑鼠左鍵</code></td>
</tr>
<tr>
<td>向後<br>Back</td>
<td align="center">⌃-</td>
<td align="center">⌘[ <em>或</em><br>⌥⌘←</td>
</tr>
<tr>
<td>向前<br>Forward</td>
<td align="center">⇧⌃-</td>
<td align="center">⌘] <em>或</em><br>⌥⌘→</td>
</tr>
<tr>
<td>切換整合式終端機<br>Terminal</td>
<td align="center">⌃`</td>
<td align="center">⌥F12</td>
</tr>
<tr>
<td>重新開啟已關閉的編輯器<br>Recent Files*</td>
<td align="center">⇧⌘T</td>
<td align="center">⌘E</td>
</tr>
</tbody>
</table><blockquote>
<p>* 註:此為相似功能,可以到 Keymap 自行設定 <code>Reopen Closed Tab</code></p>
</blockquote>
<h2 id="murmur...">Murmur…</h2>
<p><s>還沒背起來的各種抱怨與理由</s><br>
不是啊!按鍵太多了吧,指頭都要打結了 😢<br>
然後開個終端機還要按 F12,在 Touch bar 版本的 Macbook Pro 超不方便的吧…</p>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-51301879668852774502018-10-29T17:47:00.001+08:002018-10-29T17:47:36.105+08:00如何正確在 Laravel 撰寫 PHPUnit 單元測試(Unit Test)<p>本篇文章內容主要參考自 <a href="https://laracasts.com/">Laracasts</a> 的 <a href="https://laracasts.com/series/laravel-from-scratch-2017/episodes/22">Laravel 5.4 From Scratch: Testing 101</a>,並且替換掉舊有寫法,改寫成新版(Laravel 5.6)的版本。</p>
<p>我們會撰寫一個簡單的 Unit Test 測試,可以在獨立的測試資料庫中測試 model 操作 CRUD,同時不會因為測試 Create 操作而造成資料庫無限肥大下去。</p>
<hr>
<h2 id="建立測試案例">建立測試案例</h2>
<p>先建立一個測試案例,本篇以單元測試為例建立一個 PostTest.php。</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token comment"># Create a test in the Unit directory...</span>
php artisan make:test PostTest --unit
</code></pre>
<hr>
<h2 id="規劃測試內容">規劃測試內容</h2>
<p>例如我們想要測試以下 Post model 的 method 是否正常:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token shell-comment comment"># app/Post.php</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">archives</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token keyword">static</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">selectRaw</span><span class="token punctuation">(</span><span class="token string">'year(created_at) year, monthname(created_at) month, count(*) published'</span><span class="token punctuation">)</span>
<span class="token operator">-</span><span class="token operator">></span><span class="token function">groupBy</span><span class="token punctuation">(</span><span class="token string">'year'</span><span class="token punctuation">,</span> <span class="token string">'month'</span><span class="token punctuation">)</span>
<span class="token operator">-</span><span class="token operator">></span><span class="token function">orderByRaw</span><span class="token punctuation">(</span><span class="token string">'min(created_at) desc'</span><span class="token punctuation">)</span>
<span class="token operator">-</span><span class="token operator">></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token operator">-</span><span class="token operator">></span><span class="token function">toArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>我們可以先依據 Given-When-Then 格式來撰寫註解,規劃好我們要寫的測試內容。關於 Given-When-Then 可以參考本篇文末 <strong>延伸閱讀</strong> 段落的整理。</p>
<pre class=" language-php"><code class="prism language-php"><span class="token shell-comment comment"># tests/Unit/PostTest.php</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">testArchives</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token comment">// Given I have two records in the database that art posts,</span>
<span class="token comment">// and each one is posted a month apart.</span>
<span class="token comment">// When I fetch the archives.</span>
<span class="token comment">// Then the response should be in the proper format.</span>
<span class="token punctuation">}</span>
</code></pre>
<hr>
<h2 id="建立測試資料">建立測試資料</h2>
<p>在這裡,我們需要兩筆資料來測試。這時我們可以使用 <code>Model Factories</code> 來產生我們需要的測試資料到資料庫中。關於 <code>Model Factories</code> 以及資料庫測試的詳細說明,可以參考 <a href="https://laravel.com/docs/5.6/database-testing">官方文件</a> 或 <a href="https://docs.laravel-dojo.com/laravel/5.5/database-testing">道場的翻譯文件</a>。</p>
<p>我們先產生一個 Post 的 <code>Factory</code>:</p>
<pre class=" language-bash"><code class="prism language-bash">php artisan make:factory PostFactory --model<span class="token operator">=</span>Post
</code></pre>
<p><code>Factory</code> 可以配合 <code>Faker</code> 自動隨機產生我們需要的內容到資料庫中:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token shell-comment comment"># database/factories/PostFactory.php</span>
<span class="token variable">$factory</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">define</span><span class="token punctuation">(</span>App\<span class="token package">Post</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>Faker <span class="token variable">$faker</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token punctuation">[</span>
<span class="token string">'user_id'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token function">factory</span><span class="token punctuation">(</span>App\<span class="token package">User</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">id</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">'title'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$faker</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">sentence</span><span class="token punctuation">,</span>
<span class="token string">'body'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$faker</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">paragraph</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>Faker 可以產生非常多樣化的假資料,可以到 <a href="https://github.com/fzaninotto/Faker">fzaninotto/Faker</a> 看看各種稀奇古怪的假資料。</p>
<p>完成 <code>Factory</code> 後,我們就可以拿來用在測試案例的 Given 段落中,建立兩筆 Post,並且第二篇手動指定建立時間為一個月前。When 段落則是執行我們要測試的 method。Then 段落比較執行結果與我們預期的相不相同。</p>
<p>關於 Then 段落可以使用的其他斷言方法,可以查看本篇文末 <strong>延伸閱讀</strong> 段落的整理。</p>
<p>於是,我們的測試就長得像下方這樣:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token shell-comment comment"># tests/Unit/PostTest.php</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">testArchives</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token comment">// Given I have two records in the database that art posts,</span>
<span class="token comment">// and each one is posted a month apart.</span>
<span class="token variable">$first</span> <span class="token operator">=</span> <span class="token function">factory</span><span class="token punctuation">(</span>Post<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$second</span> <span class="token operator">=</span> <span class="token function">factory</span><span class="token punctuation">(</span>Post<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
<span class="token string">'created_at'</span> <span class="token operator">=</span><span class="token operator">></span> \<span class="token package">Carbon<span class="token punctuation">\</span>Carbon</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">subMonth</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// When I fetch the archives.</span>
<span class="token variable">$posts</span> <span class="token operator">=</span> Post<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">archives</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Then the response should be in the proper format.</span>
<span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">assertCount</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token variable">$posts</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>我們先簡單測試是否有抓到兩篇文章就好,之後再來改良它。</p>
<p>試試看執行測試,如果 posts 資料表真的只有兩篇文,archives method 也正確執行的話,我們就會看到綠燈:</p>
<pre class=" language-bash"><code class="prism language-bash">phpunit tests/Unit/PostTest.php
</code></pre>
<p>但是有一個問題,我們每次執行測試會建立資料在我們的資料庫中,這樣會跟其他的資料混在一起吧?如果我們 local 的 posts 表本身就超過 2 篇這個測試就不會通過了?</p>
<hr>
<h2 id="建立測試資料庫">建立測試資料庫</h2>
<p>為了避免測試資料與一般資料混在一起,我們可以建立一個測試資料庫。以 MySQL 為例,先登入 MySQL:</p>
<pre class=" language-bash"><code class="prism language-bash">mysql -uroot -p
</code></pre>
<p>然後新增一個資料庫,例如這裡我們命名為「blog_testing」:</p>
<pre class=" language-sql"><code class="prism language-sql"><span class="token keyword">create</span> <span class="token keyword">database</span> blog_testing<span class="token punctuation">;</span>
</code></pre>
<p>接著我們可以到專案根目錄的 phpunit.xml 指定測試時要用的資料庫。這個檔案會在執行測試時暫時覆寫 Laravel 的環境變數,像是將 <code>APP_ENV</code> 改為「testing」。我們在 php 區塊新增一個 <code>DB_DATABASE</code> 環境變數,並指定為「blog_testing」:</p>
<pre class=" language-xml"><code class="prism language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>php</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>APP_ENV<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>testing<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>CACHE_DRIVER<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>array<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>SESSION_DRIVER<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>array<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>QUEUE_DRIVER<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sync<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DB_DATABASE<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>blog_testing<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>php</span><span class="token punctuation">></span></span>
</code></pre>
<p>這時如果我們執行測試,會發現噴一堆錯誤,因為還沒有針對 blog_testing 做 migration 啊!那怎麼做才能對測試資料庫做 migration 呢?比較正規的做法是在 config/database.php 中新增一個全新的 connection,但這樣為了 migration 大費周章有點麻煩。</p>
<p>另一個比較 <s>髒</s> 簡便的方法,就是暫時在 .env 中把 <code>DB_DATABASE</code> 也改成「blog_testing」,然後執行 :</p>
<pre class=" language-bash"><code class="prism language-bash">php artisan migrate
</code></pre>
<p>這樣就很快速的搞定了。記得要把 .env 改回來啊!</p>
<p>然後我們再試試看執行測試,如果順利我們就會看到綠燈:</p>
<pre class=" language-bash"><code class="prism language-bash">phpunit tests/Unit/PostTest.php
</code></pre>
<p>但是又有一個問題,我們每次執行測試都會多兩筆資料,舊的 faker 資料又用不到了很礙眼啊!總不能每次都要去資料庫手動刪除吧?</p>
<hr>
<h2 id="在每次測試後重置資料庫">在每次測試後重置資料庫</h2>
<p>在 Laravel 5.4 以前,我們可以使用 <code>DatabaseTransation</code> 這個 Trait 來協助我們 rollback 成執行前的樣子。在 Laravel 5.5 以後則是改用更為強大的 <code>RefreshDatabase</code> 這個 Trait。詳細的比較說明可以參考 Laracasts 的 <a href="https://laracasts.com/series/whats-new-in-laravel-5-5/episodes/14">What’s New in Laravel 5.5: The RefreshDatabase Trait</a>。</p>
<p>所以只要在你的測試類別裡的第一行加上,Laravel 就會幫你處理所有事情:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">use</span> <span class="token package">RefreshDatabase</span><span class="token punctuation">;</span>
</code></pre>
<p>詳細可以參考 <a href="https://laravel.com/docs/5.6/database-testing#resetting-the-database-after-each-test">官方文件</a> 或是 <a href="https://docs.laravel-dojo.com/laravel/5.5/database-testing#resetting-the-database-after-each-test">道場的翻譯文件</a></p>
<hr>
<h2 id="改進測試案例">改進測試案例</h2>
<p>現在,我們想要更精準地撰寫測試案例,例如想要知道 When 區段撈出來的 $posts 資料真的是我們在 Given 建立的資料可以怎麼做呢?</p>
<p>以這次的測試為例,我們可以在 Then 區段改成用 <code>assertEquals</code> 來判斷:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">assertEquals</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
<span class="token punctuation">[</span>
<span class="token string">"year"</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$first</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">created_at</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'Y'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">"month"</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$first</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">created_at</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'F'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">"published"</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token number">1</span>
<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">[</span>
<span class="token string">"year"</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$second</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">created_at</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'Y'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">"month"</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token variable">$second</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">created_at</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'F'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">"published"</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token number">1</span>
<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$posts</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>這樣就可以確認我們在 When 區段所撈取的就是 Given 區段建立的文章了。</p>
<hr>
<h2 id="參考資料與延伸閱讀">參考資料與延伸閱讀</h2>
<h3 id="laracasts">Laracasts</h3>
<ul>
<li><a href="https://laracasts.com/series/laravel-from-scratch-2017/episodes/22">Laravel 5.4 From Scratch: Testing 101</a></li>
<li><a href="https://laracasts.com/series/whats-new-in-laravel-5-5/episodes/14">What’s New in Laravel 5.5: The RefreshDatabase Trait</a></li>
</ul>
<h3 id="given-when-then">Given-When-Then</h3>
<ul>
<li><a href="http://jaceju.net/2015-05-31-skilltree-tdd-3/">自動測試與 TDD 實務開發 - 上課心得 (下) | 網站製作學習誌</a></li>
<li><a href="http://teddy-chen-tw.blogspot.com/2017/03/bdd17given-when-then.html">搞笑談軟工: BDD(17)Given-When-Then怎麼寫?</a></li>
</ul>
<h3 id="斷言方法">斷言方法</h3>
<ul>
<li>PHPUnit 提供的 <a href="https://phpunit.readthedocs.io/en/latest/assertions.html">斷言方法列表</a>(也有<a href="https://phpunit.readthedocs.io/zh_CN/latest/assertions.html">簡體中文版</a>)</li>
<li>Laravel 提供的 <a href="https://laravel.com/docs/5.6/database-testing#available-assertions">資料庫測試斷言方法</a>(<a href="https://docs.laravel-dojo.com/laravel/5.5/database-testing#available-assertions">道場翻譯版</a>)</li>
<li>Laravel 提供的 <a href="https://laravel.com/docs/5.6/http-tests#available-assertions">HTTP 測試斷言方法</a>(<a href="https://docs.laravel-dojo.com/laravel/5.5/http-tests#available-assertions">道場翻譯版</a>)</li>
<li>Laravel Dusk 提供的 <a href="https://laravel.com/docs/5.6/dusk#available-assertions">瀏覽器測試斷言方法</a>(<a href="https://docs.laravel-dojo.com/laravel/5.5/dusk#available-assertions">道場翻譯版</a>)</li>
</ul>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-34382814842233573552018-09-19T10:20:00.001+08:002018-09-19T10:20:03.949+08:00[筆記] 如何在 Laravel 測試中使用 Faker<p>是這樣的,我想在 Test 中產生一些假資料丟給 Service method 跑,於是很直覺地想到了 <a href="https://github.com/fzaninotto/Faker">Faker</a> 這個很棒很方便的東西。</p>
<p>一般我們使用 Faker 是 <a href="https://laravel.com/docs/5.7/database-testing">搭配 Model Factory 使用</a>。但是我這次只是想產生個簡單的假資料,跟我的資料庫或 Model 都沒有關係,有沒有辦法?</p>
<p><img src="https://images.pexels.com/photos/34600/pexels-photo.jpg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260" alt="只是放一個封面圖"></p>
<blockquote>
<p>只是放一個封面圖</p>
</blockquote>
<h2 id="隆重介紹:withfaker-trait">隆重介紹:WithFaker trait</h2>
<p>Laravel <a href="https://github.com/laravel/framework/pull/22280">從 5.5.24 版開始</a> 已經內建了這個 trait,而且在你使用 <code>artisan</code> 指令建立測試檔案時就會自動幫你導入命名空間了:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Foundation<span class="token punctuation">\</span>Testing<span class="token punctuation">\</span>WithFaker</span><span class="token punctuation">;</span>
</code></pre>
<p>結果到現在已經 5.7 版了,竟然還沒有在教學文件出現過。可以自行參考 <a href="https://laravel.com/api/5.7/Illuminate/Foundation/Testing/WithFaker.html">Laravel API 文件</a>。</p>
<h2 id="如何使用-withfaker">如何使用 WithFaker</h2>
<p>現在只需使用 trait 在你的測試類別上,例如:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token shell-comment comment"># tests/Unit/ExampleTest.php</span>
<span class="token comment">// 上略</span>
<span class="token keyword">class</span> <span class="token class-name">ExampleTest</span> <span class="token keyword">extends</span> <span class="token class-name">TestCase</span>
<span class="token punctuation">{</span>
<span class="token keyword">use</span> <span class="token package">WithFaker</span><span class="token punctuation">;</span>
<span class="token comment">// 下略</span>
</code></pre>
<p>接著就可以透過 <code>$this->faker</code> 來操作 Faker 了,例如:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">echo</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">faker</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">name</span><span class="token punctuation">;</span>
<span class="token comment">// 'Lucy Cechtelar';</span>
<span class="token keyword">echo</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">faker</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">address</span><span class="token punctuation">;</span>
<span class="token comment">// "426 Jordy Lodge</span>
<span class="token comment">// Cartwrightshire, SC 88120-6700"</span>
<span class="token keyword">echo</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">faker</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">text</span><span class="token punctuation">;</span>
<span class="token comment">// Dolores sit sint laboriosam dolorem culpa et autem. Beatae nam sunt fugit</span>
<span class="token comment">// et sit et mollitia sed.</span>
<span class="token comment">// Fuga deserunt tempora facere magni omnis. Omnis quia temporibus laudantium</span>
<span class="token comment">// sit minima sint.</span>
</code></pre>
<p>是不是非常簡單呢?</p>
<h2 id="參考資料">參考資料</h2>
<ul>
<li><a href="https://stackoverflow.com/questions/50318048/laravel-how-to-use-faker-in-phpunit-test">Laravel - How to use faker in PHPUnit test? - Stack Overflow</a></li>
<li><a href="https://twitter.com/laravellog/status/938821670390960128">Laravel Changelog 在 Twitter:laravel 5.5.24 Added WithFaker testing trait</a></li>
<li><a href="https://medium.com/@brice_hartmann/testing-laravel-password-resets-858c58c16b79">Testing Laravel Password Resets</a></li>
<li><a href="https://laravel.com/api/5.7/Illuminate/Foundation/Testing/WithFaker.html">Illuminate\Foundation\Testing\WithFaker | Laravel API</a></li>
</ul>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-38955970793369170302018-09-14T13:11:00.003+08:002018-09-14T13:11:55.970+08:00[筆記] Faker 如何產生 10 位數以上的 random number?<p>在寫 Laravel 測試需要的 Factory 時,有個欄位需要 16 位數以下的數字。一般來說直接透過 <a href="https://github.com/fzaninotto/Faker">fzaninotto/Faker</a> 的 <code>randomNumber</code> 就可以很方便的生成了:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$number</span> <span class="token operator">=</span> <span class="token variable">$faker</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">randomNumber</span><span class="token punctuation">(</span><span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>結果執行的時候竟然噴錯了!</p>
<blockquote>
<p>InvalidArgumentException: randomNumber() can only generate numbers up to mt_getrandmax()</p>
</blockquote>
<h2 id="mt_getrandmax-是什麼">mt_getrandmax() 是什麼</h2>
<p>錯誤訊息中提到的 <a href="http://php.net/manual/en/function.mt-getrandmax.php"><code>mt_getrandmax()</code></a> 是 PHP 的內建函式,會回傳 PHP 透過 <code>mt_rand()</code> 可以產生的最大數。</p>
<p>這個 <a href="http://php.net/manual/en/function.mt-rand.php"><code>mt_rand()</code></a> 就是一般的隨機產生函式,只要有指定 min 和 max,其實他是可以產生更大數的,不是很理解為什麼 Faker 沒有做這個處理。</p>
<h2 id="可以怎麼做?">可以怎麼做?</h2>
<p>這裡我想了幾個方法,可以給各位參考。</p>
<h3 id="方法一:直接使用-mt_rand-或-random_int">方法一:直接使用 mt_rand 或 random_int</h3>
<p>既然他背後是使用 <code>mt_rand()</code>,那就直接使用它吧。</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$number</span> <span class="token operator">=</span> <span class="token function">mt_rand</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">9999999999999999</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>不過我實際使用發現,雖然我是只有上限給了 16 位數,他產生的結果好像是固定十六位數。</p>
<p>後來嘗試了 PHP 7 新的 <a href="http://php.net/manual/en/function.random-int.php"><code>random_int</code></a>,結果差不多。</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$number</span> <span class="token operator">=</span> <span class="token function">random_int</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">9999999999999999</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>PHP 官網文件在 <code>mt_getrandmax</code> 的頁面上有提及,如果提供 MAX 給 <code>mt_rand</code> 會減少隨機性,不知道是不是這個原因。</p>
<hr>
<h3 id="方法二:使用-numberbetween">方法二:使用 numberBetween</h3>
<p>Faker 有提供 <code>numberBetween</code>,所以可以直接使用:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$number</span> <span class="token operator">=</span> <span class="token variable">$faker</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">numberBetween</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">9999999999999999</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>因為 <code>numberBetween</code> 也是基於 <code>mt_rand</code>,我實測結果好像只比 <code>mt_rand</code> 好一點。</p>
<p>以上兩個方法如果有人知道如何修正,再麻煩留言跟我說一下。</p>
<hr>
<h3 id="方法三:使用-numerify">方法三:使用 numerify</h3>
<p>這個是 Faker 作者在 <a href="https://github.com/fzaninotto/Faker/issues/365#issuecomment-48052371">類似的 issue</a> 裡提出的做法,我把它替換成這個情境的解法。</p>
<p><code>numerify</code> 是 Faker 的另一個函式,可以在字串的指定位置裡塞入數字。官方範例:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token function">numerify</span><span class="token punctuation">(</span><span class="token string">'Hello ###'</span><span class="token punctuation">)</span> <span class="token comment">// 'Hello 609'</span>
</code></pre>
<p>搭配 PHP 內建的 <a href="http://php.net/manual/en/function.str-repeat.php">str_repeat</a>,可以重複指定次數的字串。然後我們再搭配 Faker 的 <code>numberBetween</code>,就組成以下的程式:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$numberLength</span> <span class="token operator">=</span> <span class="token variable">$faker</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">numberBetween</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$number</span> <span class="token operator">=</span> <span class="token variable">$faker</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">numerify</span><span class="token punctuation">(</span><span class="token function">str_repeat</span><span class="token punctuation">(</span><span class="token string">'#'</span><span class="token punctuation">,</span> <span class="token variable">$numberLength</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>這次的隨機性應該比較高了。</p>
<h2 id="後記">後記</h2>
<p>我最終採用了方法三。我還有<a href="https://stackoverflow.com/questions/8215979/php-random-x-digit-number">查到有些人</a> 用 <code>pow</code> 來決定位數。如果你有其他不錯的作法,也歡迎在下方留言告訴我~</p>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-80572907884199196992018-08-31T17:20:00.001+08:002018-08-31T17:20:02.773+08:00[筆記] Facebook Open Graph 的 og:locale 該如何設定?<p>Facebook 的開放社交關係圖(Open Graph)針對 <code>og:locale</code> 的規範不曉得是不是有更新,以至於檢查網頁抓取資訊的時候噴了一個我沒看過的錯:<br>
<img src="https://i.imgur.com/aVtzYOx.png" alt="enter image description here"></p>
<blockquote>
<p>「 website 」類型的 URL「 <a href="https://www.example.com">https://www.example.com</a> 」無效,因為「 og:locale:locale 」特性的指定值「 XX 」無法作為「 enum 」類型進行剖析。<br>
Object at URL ‘<a href="https://www.example.com">https://www.example.com</a>’ of type ‘website’ is invalid because the given value ‘XX’ for property ‘og:locale:locale’ could not be parsed as type ‘enum’.</p>
</blockquote>
<hr>
<p>查了一下發現是語言格式錯了,Facebook 的格式不是完全依照 ISO 標準,依據<a href="https://developers.facebook.com/docs/internationalization?locale=zh_TW#locales">本地化文件</a>的說明:</p>
<blockquote>
<p>Facebook 上的語言使用 ll_CC 格式,ll 是兩個字母的語言代碼,CC 是兩個字母的國家/地區代碼。例如,en_US 代表美式英文。 我們支援許多 ISO 語言和國碼/區碼</p>
</blockquote>
<hr>
<p>從 <a href="https://developers.facebook.com/docs/reference/opengraph/object-type/website/">SDK 文件</a>裡面可以看到目前 Facebook 支援的 <code>og:locate</code> 列舉如下:</p>
<blockquote>
<p>Allowed values: en_us, ca_es, cs_cz, cx_ph, cy_gb, da_dk, de_de, eu_es, en_pi, en_ud, ck_us, es_la, es_es, es_mx, gn_py, fi_fi, fr_fr, gl_es, ht_ht, hu_hu, it_it, ja_jp, ko_kr, nb_no, nn_no, nl_nl, fy_nl, pl_pl, pt_br, pt_pt, ro_ro, ru_ru, sk_sk, sl_si, sv_se, th_th, tr_tr, ku_tr, zh_cn, zh_hk, zh_tw, fb_lt, af_za, sq_al, hy_am, az_az, be_by, bn_in, bs_ba, bg_bg, hr_hr, nl_be, en_gb, eo_eo, et_ee, fo_fo, fr_ca, ka_ge, el_gr, gu_in, hi_in, is_is, id_id, ga_ie, jv_id, kn_in, kk_kz, ky_kg, la_va, lv_lv, li_nl, lt_lt, mi_nz, mk_mk, mg_mg, ms_my, mt_mt, mr_in, mn_mn, ne_np, pa_in, rm_ch, sa_in, sr_rs, so_so, sw_ke, tl_ph, ta_in, tt_ru, te_in, ml_in, uk_ua, uz_uz, vi_vn, xh_za, zu_za, km_kh, tg_tj, ar_ar, he_il, ur_pk, fa_ir, sy_sy, yi_de, qc_gt, qu_pe, ay_bo, se_no, ps_af, tl_st, gx_gr, my_mm, qz_mm, or_in, si_lk, rw_rw, ak_gh, nd_zw, sn_zw, cb_iq, ha_ng, yo_ng, ja_ks, lg_ug, br_fr, zz_tr, tz_ma, co_fr, ig_ng, as_in, am_et, lo_la, ny_mw, wo_sn, ff_ng, sc_it, ln_cd, tk_tm, sz_pl, bp_in, ns_za, tn_bw, st_za, ts_za, ss_sz, ks_in, ve_za, nr_za, ik_us, su_id, om_et, em_zm and qr_gr</p>
</blockquote>
<p>結果自己 SDK 文件裡的語言格式,最後兩字母都沒有大寫呢,呵呵。</p>
<hr>
<h2 id="參考資料">參考資料</h2>
<ul>
<li><a href="https://kb.yoast.com/kb/changing-the-og-locale-output/#change">Changing the og:locale output - Yoast Knowledge Base</a></li>
<li><a href="https://developers.facebook.com/docs/internationalization?locale=zh_TW">本地化和翻譯 - 圖形 API</a></li>
<li><a href="https://developers.facebook.com/docs/reference/opengraph/object-type/website/">Open Graph Object Type</a></li>
</ul>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-9947647736951542522018-08-31T17:02:00.001+08:002018-08-31T17:02:44.840+08:00[筆記] 延長 PHP-FPM 和 NGINX 執行時間上限<p>Laravel 或是一般 PHP 專案上常會遇到執行時間超時的問題,<br>
例如遇到了 PHP 超過執行時間上限的錯誤:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token string">'production.ERROR: Maximum execution time of 30 seconds exceeded'</span>
</code></pre>
<p>或是遇到 NGINX 的超時錯誤:</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token string">'504 Gateway Time-out'</span>
</code></pre>
<p>是因為 PHP 和 NGINX 預設最長執行時間都是 30 秒,我們可以修改設定來延長它們。這裡我們延長到 Apache、IIS 慣例的 5 分鐘。</p>
<p>這個主題其實很多人寫了,但是資訊有點零散,乾脆自己再寫一篇方便以後找。<s>(感覺就是很常會改啊)</s></p>
<h2 id="php-延長-max_execution_time">PHP: 延長 <code>max_execution_time</code></h2>
<p>PHP 的 runtime 設定中的 <code>max_execution_time</code> 參數可以指定腳本被 parser 中止之前允許的最大執行時間,預設值是 30 秒。我們可以透過修改 php.ini 指定為 300 秒。</p>
<h3 id="修改-php.ini">修改 php.ini</h3>
<p>以 PHP 7.2 為例,php.ini 的位置預設是在 <code>/etc/php/7.2/cli/php.ini</code>。php.ini 的位置可以經由 <code>php -i</code> 指令查詢的到。順帶一提,PHPBrew 使用者可以透過 <code>phpbrew config</code> 指令直接開啟當前版本對應的 php.ini。</p>
<pre class=" language-ini"><code class="prism language-ini"><span class="token comment">; /etc/php/7.2/cli/php.ini</span>
<span class="token comment">;;;;;;;;;;;;;;;;;;;</span>
<span class="token comment">; Resource Limits ;</span>
<span class="token comment">;;;;;;;;;;;;;;;;;;;</span>
<span class="token comment">; Maximum execution time of each script, in seconds</span>
<span class="token comment">; http://php.net/max-execution-time</span>
<span class="token comment">; Note: This directive is hardcoded to 0 for the CLI SAPI</span>
<span class="token constant">max_execution_time</span> <span class="token attr-value"><span class="token punctuation">=</span> 300</span>
</code></pre>
<h3 id="重新啟動-php-fpm">重新啟動 PHP-FPM</h3>
<p>修改完成後,需要重新啟動 PHP-FPM。</p>
<ul>
<li>PHPBrew 使用者</li>
</ul>
<pre class=" language-bash"><code class="prism language-bash">phpbrew fpm restart
</code></pre>
<ul>
<li>Ubuntu/Debian 系</li>
</ul>
<pre class=" language-bash"><code class="prism language-bash"><span class="token function">sudo</span> <span class="token function">service</span> php7.2-fpm restart
</code></pre>
<ul>
<li>CentOS/RHEL 7 系</li>
</ul>
<pre class=" language-bash"><code class="prism language-bash"><span class="token function">sudo</span> systemctl restart php-fpm
</code></pre>
<h2 id="nginx-延長-fastcgi_read_timeout">NGINX: 延長 <code>fastcgi_read_timeout</code></h2>
<p>NGINX 的部分則是透過 <code>fastcgi_read_timeout</code> 這個參數來決定 PHP FastCGI Server(PHP-FPM)的 Response 時間限制,預設是 30 秒,我們也把它改成 300 秒。</p>
<h3 id="修改-nginx.conf">修改 nginx.conf</h3>
<p>這裡我們直接在 <code>/etc/nginx/nginx.conf</code> 的 <code>http</code> 內容區塊中設定,就會套用到所有站點。你也可以選擇在各自的 Server config 裡的 <code>server</code> 或 <code>location</code> 內容區塊中指定不同的時間限制。</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token comment"># /etc/nginx/nginx.conf</span>
http <span class="token punctuation">{</span>
<span class="token comment">##</span>
<span class="token comment"># Basic Settings</span>
<span class="token comment">##</span>
<span class="token comment"># 以上省略...</span>
fastcgi_read_timeout 300s<span class="token punctuation">;</span>
<span class="token comment"># 以下省略...</span>
</code></pre>
<h3 id="重新載入設定">重新載入設定</h3>
<p>修改完成後,需要讓 NGINX 讀取新的設定檔。</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token function">sudo</span> nginx -t <span class="token comment"># 測試設定檔有無問題</span>
<span class="token function">sudo</span> nginx -s reload <span class="token comment"># 重新讀取設定檔</span>
</code></pre>
<h2 id="關於-php-fpm-的額外設定">關於 PHP-FPM 的額外設定</h2>
<p>在 <a href="https://easyengine.io/tutorials/php/increase-script-execution-time/">Increase PHP script execution time with Nginx</a> 這篇文中還有提到,如果你曾經設定過 PHP-FPM 的 <code>request_terminate_timeout</code>,也要記得改哦!</p>
<p>這個值預設是被註解掉、直接關聯到 php.ini 的 <code>max_execution_time</code>,官方建議 <code>max_execution_time</code> 有問題的話才使用它。</p>
<p>如果要修改或檢查的話,預設是在 <code>/etc/php/7.2/fpm/pool.d/www.conf</code> 修改。PHPBrew 使用者可以透過 <code>phpbrew fpm config</code> 打開當前版本的 www.conf 檔。</p>
<pre class=" language-ini"><code class="prism language-ini"><span class="token comment">; /etc/php/7.2/fpm/pool.d/www.conf</span>
<span class="token constant">request_terminate_timeout</span> <span class="token attr-value"><span class="token punctuation">=</span> 300</span>
</code></pre>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-89996797637951976842018-07-25T17:07:00.001+08:002018-07-25T17:07:03.809+08:00PHPBrew:編譯 PHP 的各種踩雷紀錄<p>PHPBrew 是由 C9S 大神產出來的神作,一直以來都用它維護伺服器上的 PHP 版本,但時常會有一些小問題要解決。這次剛好要裝到新機器上,就順便記錄一下遇到的一些狀況。</p>
<hr>
<h2 id="系統環境">系統環境</h2>
<p>新開的 VPS 主機,作業系統為 CentOS 7.5,已經用 yum update 更新到最新環境,有啟用 epel-release repository。</p>
<hr>
<h2 id="requirement">Requirement</h2>
<p>先安裝官方 Wiki 指定的基本套件,使用的是以下段落:<br>
<a href="https://github.com/phpbrew/phpbrew/wiki/Requirement#fedoracentos-requirements">https://github.com/phpbrew/phpbrew/wiki/Requirement#fedoracentos-requirements</a></p>
<p>這頁面的另一個段落(<a href="https://github.com/phpbrew/phpbrew/wiki/Requirement#centos-requirement">CentOS Requirement</a>)親身實驗確定已失效,<strong><a href="http://repoforge.org/">repoforge.org 已經掛很久啦</a></strong>!!</p>
<pre class=" language-bash"><code class="prism language-bash">yum <span class="token function">install</span> <span class="token function">make</span> automake gcc gcc-c++ kernel-devel
yum <span class="token function">install</span> php php-devel php-pear bzip2-devel yum-utils bison re2c libmcrypt-devel libpqxx-devel libxslt-devel pcre-devel libcurl-devel libgsasl-devel openldap-devel
yum-builddep php
yum <span class="token function">install</span> httpd-devel
</code></pre>
<p>以及我自己需要裝的相依,注意 <code>readline</code> 是有包含在 <code>+default</code> 的,如果你也是指定 <code>+default</code> ,記得安裝。</p>
<pre class=" language-bash"><code class="prism language-bash">yum <span class="token function">install</span> readline-devel ImageMagick-devel libmemcached-devel
</code></pre>
<hr>
<h2 id="installing-phpbrew-into-system-wide-environment">Installing PHPBrew into System wide Environment</h2>
<p>參考資料:<a href="https://github.com/phpbrew/phpbrew/wiki/Installing-PHPBrew-into-System-wide-Environment">https://github.com/phpbrew/phpbrew/wiki/Installing-PHPBrew-into-System-wide-Environment</a></p>
<p>如果想要讓編譯的 PHP 可以給所有使用者使用,就使用 root 來編譯吧。</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token function">sudo</span> -i <span class="token comment"># 切換成 root 身份</span>
phpbrew init
</code></pre>
<h3 id="增加環境變數到-phpbrew-的-bashrc">增加環境變數到 PHPBrew 的 bashrc</h3>
<pre class=" language-bash"><code class="prism language-bash">vim ~/.phpbrew/bashrc
</code></pre>
<p>增加 <code>PHPBREW_ROOT</code> 和 <code>PHPBREW_HOME</code> 環境變數到開頭。</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token comment"># /.phpbrew/bashrc</span>
<span class="token function">export</span> PHPBREW_ROOT<span class="token operator">=</span>/opt/phpbrew <span class="token comment"># php dist files and build files are stored here</span>
<span class="token function">export</span> PHPBREW_HOME<span class="token operator">=</span>/root/.phpbrew <span class="token comment"># your configuration files.</span>
</code></pre>
<p>重新載入</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token function">source</span> ~/.phpbrew/bashrc
</code></pre>
<h3 id="設定登入自動執行檔">設定登入自動執行檔</h3>
<p>接著設定所有使用者的登入自動執行檔,讓大家可以使用同一個 PHP 版本。</p>
<p>這裡註記一下,官方 Wiki 建議放在 <code>/etc/profile.d/phpbrew</code> 是錯的。系統預設只會載入 <code>*.sh</code> 的腳本,所以應該要命名為 <code>phpbrew.sh</code>。</p>
<pre class=" language-bash"><code class="prism language-bash">vim /etc/profile.d/phpbrew.sh
</code></pre>
<p>增加 <code>PHPBREW_ROOT</code> 和 <code>PHPBREW_HOME</code> 環境變數到開頭。</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token comment"># /etc/profile.d/phpbrew.sh</span>
<span class="token function">export</span> PHPBREW_ROOT<span class="token operator">=</span>/opt/phpbrew
<span class="token function">export</span> PHPBREW_HOME<span class="token operator">=</span><span class="token variable">$HOME</span>/.phpbrew
<span class="token punctuation">[</span><span class="token punctuation">[</span> -e ~/.phpbrew/bashrc <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&&</span> <span class="token function">source</span> ~/.phpbrew/bashrc
</code></pre>
<p>官方 Wiki 的 Cookbook 把 <code>PHPBREW_HOME</code> 寫錯了,source bashrc 的檔案則是 Cookbook 和 Installing PHPBrew into System wide Environment 都錯,詳情<strong>請看下方的錯誤紀錄</strong>。</p>
<hr>
<h2 id="setting-up">Setting up</h2>
<p>參考資料:<a href="https://github.com/phpbrew/phpbrew#setting-up">https://github.com/phpbrew/phpbrew#setting-up</a></p>
<p>更新一下 PHP 的版本列表</p>
<pre class=" language-bash"><code class="prism language-bash">phpbrew update
</code></pre>
<hr>
<h2 id="starting-building-your-own-php">Starting Building Your Own PHP</h2>
<p>參考資料:<a href="https://github.com/phpbrew/phpbrew#starting-building-your-own-php">https://github.com/phpbrew/phpbrew#starting-building-your-own-php</a></p>
<p>來開始編譯 PHP 吧</p>
<pre class=" language-bash"><code class="prism language-bash">phpbrew <span class="token function">install</span> 7.1.7 +default +pdo +mysql +gd<span class="token operator">=</span>shared +fpm -- --with-fpm-systemd --with-libdir<span class="token operator">=</span>lib64
</code></pre>
<p>將系統 PHP 切換到我們編譯的 php-7.1.7</p>
<pre class=" language-bash"><code class="prism language-bash">phpbrew switch php-7.1.7
</code></pre>
<p>然後安裝需要的擴充模組</p>
<pre class=" language-bash"><code class="prism language-bash">phpbrew ext <span class="token function">install</span> imagick
phpbrew ext <span class="token function">install</span> memcached
phpbrew ext <span class="token function">install</span> redis
phpbrew ext <span class="token function">install</span> gd -- --with-gd<span class="token operator">=</span>shared --enable-gd-native-ttf --with-jpeg-dir<span class="token operator">=</span>/usr/lib64 --with-png-dir<span class="token operator">=</span>/usr/lib64
</code></pre>
<hr>
<h2 id="php-設定">PHP 設定</h2>
<p>接著就可以編輯一些設定了</p>
<h3 id="設定-php.ini">設定 php.ini</h3>
<p>例如想要修改 php.ini 設定 <code>expose_php</code> 為 Off 把版本號資訊隱藏<br>
只要執行以下命令就可以打開當前版本的 php.ini</p>
<pre class=" language-bash"><code class="prism language-bash">phpbrew config
</code></pre>
<p>就會用預設的編輯器(如 nano)開啟 php.ini</p>
<p>如果你想要使用 Vim 等其他編輯器開啟<br>
記得指定 <code>EDITOR</code> 環境變數<br>
例如在 ~/.bashrc 中加入</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token function">export</span> EDITOR<span class="token operator">=</span>vim
</code></pre>
<h3 id="設定-php-fpm.conf">設定 php-fpm.conf</h3>
<p>如果想要設定如 <code>max_children</code> 等針對 FPM 的設定<br>
也是有簡單的指令即可開啟當前版本的 php-fpm.conf</p>
<pre class=" language-bash"><code class="prism language-bash">phpbrew fpm config
</code></pre>
<hr>
<h2 id="錯誤紀錄">錯誤紀錄</h2>
<p>稍微紀錄一下編譯與安裝時遇到的錯誤。</p>
<h3 id="configure-error-cannot-find-openssls-libraries">configure: error: Cannot find OpenSSL’s libraries</h3>
<p>如果要讓編譯的 PHP 支援 OpenSSL,在 Fedora/Centos 64-Bit 系統需要加上 <code>--with-libdir=lib64</code> 來編譯。可以參考以下資料:</p>
<ul>
<li><a href="https://github.com/phpbrew/phpbrew/wiki/Cookbook#openssl-support">https://github.com/phpbrew/phpbrew/wiki/Cookbook#openssl-support</a></li>
<li><a href="https://github.com/phpbrew/phpbrew/wiki/Extension-Installer#fedoracentos-64-bit">https://github.com/phpbrew/phpbrew/wiki/Extension-Installer#fedoracentos-64-bit</a></li>
<li><a href="https://github.com/phpbrew/phpbrew/issues/785#issuecomment-243427848">https://github.com/phpbrew/phpbrew/issues/785#issuecomment-243427848</a></li>
</ul>
<hr>
<h3 id="configure-error-please-reinstall-readline---i-cannot-find-readline.h">configure: error: Please reinstall readline - I cannot find readline.h</h3>
<p>如果你是使用 <code>+default</code> 選項,就會需要先安裝 <code>readline-devel</code>。參考資料:</p>
<ul>
<li><a href="https://github.com/phpbrew/phpbrew/wiki/TroubleShooting#configure-error-please-reinstall-readline---i-cannot-find-readlineh">https://github.com/phpbrew/phpbrew/wiki/TroubleShooting#configure-error-please-reinstall-readline---i-cannot-find-readlineh</a></li>
</ul>
<hr>
<h3 id="phpbrew-安裝成全域(system-wide),重登入卻沒抓到">PHPBrew 安裝成全域(system-wide),重登入卻沒抓到</h3>
<p>文件建議把環境變數放在 <code>/etc/profile.d/phpbrew</code>,這部分寫錯了。<br>
<code>/etc/bashrc</code> 預設只會載入 <code>/etc/profile.d/*.sh</code>,所以應該要命名為 <code>/etc/profile.d/phpbrew.sh</code>。</p>
<hr>
<h3 id="bash-optphpbrewbashrc-no-such-file-or-directory">-bash: /opt/phpbrew/bashrc: No such file or directory</h3>
<p>Cookbook 和 Installing PHPBrew into System wide Environment 都這樣指定,卻沒有這個檔案,不知道為什麼。我暫時改為 <code>[[ -e ~/.phpbrew/bashrc ]] && source ~/.phpbrew/bashrc</code>。參考以下資料:</p>
<ul>
<li><a href="https://github.com/phpbrew/phpbrew/issues/903">https://github.com/phpbrew/phpbrew/issues/903</a></li>
</ul>
<hr>
<h3 id="module-某擴充模組-already-loaded-in-unknown-on-line-0">module ‘某擴充模組’ already loaded in unknown on line 0</h3>
<p>我遇到的是 <code>module 'curl' already loaded in unknown on line 0</code>,通常是因為編譯的時候重複指定了 Variants,例如我原本沒發現 <code>+default</code> 裡面有 curl 就又多寫了一個 <code>+curl</code>。這個解決方法我找不到,只能刪除重新編譯了。</p>
<hr>
<h3 id="如何移除某一版本-php?">如何移除某一版本 PHP?</h3>
<p>這個文件裡面沒有寫,但 <code>phpbrew help</code> 以及 <a href="https://github.com/phpbrew/phpbrew/issues/37">一些 Issue</a> 中有提到。</p>
<pre class=" language-bash"><code class="prism language-bash">phpbrew remove <span class="token punctuation">[</span>php-version<span class="token punctuation">]</span>
phpbrew purge <span class="token punctuation">[</span>php-version<span class="token punctuation">]</span>
</code></pre>
<p>要注意的是,<code>php-version</code> 要填完整名稱,不能像 <code>phpbrew switch</code> 可以只填版本號。例如 <code>phpbrew remove php-7.1.7</code>。</p>
<p>從 <code>phpbrew help</code> 的說明可以看出 <code>remove</code> 和 <code>purge</code> 是有點差異的。</p>
<pre class=" language-bash"><code class="prism language-bash">remove Remove installed php build.
purge Remove installed php version and config files.
</code></pre>
<p>另外前者會有確認提示:</p>
<pre class=" language-bash"><code class="prism language-bash">phpbrew remove php-7.1.7
<span class="token comment"># Are you sure to delete php-7.1.7? [Y/n]</span>
<span class="token comment"># php-7.1.7 is removed. I hope you're not surprised. :)</span>
</code></pre>
<p>後者則是直接刪除:</p>
<pre class=" language-bash"><code class="prism language-bash">phpbrew purge php-7.1.7
<span class="token comment"># php version: php-7.1.7 is removed and purged.</span>
</code></pre>
<hr>
<h3 id="php-warning--php-startup-invalid-library-maybe-not-a-php-library-curl.so-in-unknown-on-line-0">PHP Warning: PHP Startup: Invalid library (maybe not a PHP library) ‘<a href="http://curl.so">curl.so</a>’ in Unknown on line 0</h3>
<p>這是因為已經用 <code>+default</code> 把 curl 編譯進去了,如果又再用 <code>phpbrew ext install curl</code> 安裝擴充模組就會造成錯誤。解決方法是停用 curl 外掛:</p>
<pre class=" language-bash"><code class="prism language-bash">phpbrew ext disable curl
</code></pre>
<p>參考資料:</p>
<ul>
<li><a href="https://github.com/phpbrew/phpbrew/issues/256#issuecomment-46769001">https://github.com/phpbrew/phpbrew/issues/256#issuecomment-46769001</a></li>
</ul>
<p>但我也有執行 <code>phpbrew ext install gd</code>,不知道為什麼沒有報錯就是了⋯⋯</p>
<hr>
<h3 id="php-warning--touch-unable-to-create-file-optphpbrewbincomposer-because-no-such-file-or-directory-in-pharusrlocalbinphpbrewsrcphpbrewdownloaderbasedownloader.php-on-line-43">PHP Warning: touch(): Unable to create file /opt/phpbrew/bin/composer because No such file or directory in phar:///usr/local/bin/phpbrew/src/PhpBrew/Downloader/BaseDownloader.php on line 43</h3>
<p>執行 <code>phpbrew app get composer</code> 安裝 Composer 的時候,不知道為什麼它不能自己 mkdir,錯誤訊息是 RuntimeException: Target path (/opt/phpbrew/bin/composer) is not writable!</p>
<p>理論上 <code>~/.phpbrew/bashrc</code> 會幫你自動建好 <code>/opt/phpbrew/bin</code> 目錄,所以可能是你的 <code>~/.phpbrew/bashrc</code> 沒有設定好。</p>
<p>之後要執行的話就是使用以下指令</p>
<pre class=" language-bash"><code class="prism language-bash">php composer <span class="token function">command</span> <span class="token punctuation">[</span>options<span class="token punctuation">]</span> <span class="token punctuation">[</span>arguments<span class="token punctuation">]</span>
</code></pre>
<p>還要多打一個 php 有點麻煩,所以我還是移除改成 Composer 官方的安裝方式了。</p>
<hr>
<h2 id="終於裝完了">終於裝完了</h2>
<p>歷經好幾個小時(淚</p>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-79687872182309639212018-02-18T00:34:00.001+08:002018-02-18T00:34:37.005+08:00[筆記] Laravel One to Many 遇到 a foreign key constraint fails<p><img src="https://upload.wikimedia.org/wikipedia/commons/0/04/Entity_Relationship_Diagram_Examples.png" alt="By Chad250 CC-BY-SA-4.0"></p>
<h2 id="碰到錯誤訊息了">碰到錯誤訊息了</h2>
<p>(以下 Model 與 Table 名稱為示意,有可能因為手工改寫而有出入,看起來怪怪的地方請跟我說)</p>
<p>在儲存 Category、Product 兩 table 的 One to Many 資料時,我收到的錯誤訊息如下:</p>
<blockquote>
<p>SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`store`.`products`, CONSTRAINT `products_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`)) (SQL: insert into `products` (`name`, `category_id`) values (商品 P, 0))</p>
</blockquote>
<p>發生錯誤的程式碼簡化後類似這樣:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$category</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Category</span><span class="token punctuation">;</span>
<span class="token variable">$category</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">id</span> <span class="token operator">=</span> <span class="token string">"category01"</span><span class="token punctuation">;</span>
<span class="token variable">$category</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">name</span> <span class="token operator">=</span> <span class="token string">"分類 C"</span><span class="token punctuation">;</span>
<span class="token variable">$category</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$product</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Product</span><span class="token punctuation">;</span>
<span class="token variable">$product</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">name</span> <span class="token operator">=</span> <span class="token string">"商品 P"</span><span class="token punctuation">;</span>
<span class="token variable">$category</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">products</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">save</span><span class="token punctuation">(</span><span class="token variable">$product</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>檢查了一遍 Category 的 Model 已經設好 <code>hasMany</code>:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">products</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">hasMany</span><span class="token punctuation">(</span><span class="token string">'App\Product'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>Product 的 Model 中 <code>belongsTo</code> 也設定正確:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">category</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">belongsTo</span><span class="token punctuation">(</span><span class="token string">'App\Category'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<hr>
<h2 id="問題在這">問題在這</h2>
<p>這時我們從上面錯誤訊息可以注意到 <code>category_id</code> 的值是「0」</p>
<pre class=" language-php"><code class="prism language-php"><span class="token function">values</span> <span class="token punctuation">(</span>商品 P<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
</code></pre>
<p>這個 <code>category_id</code> 照程式碼來看應該要是「category01」,<br>
但我們要儲存 one to many 時,卻在組成 SQL 語法的時候變成了「0」?</p>
<p>我們來看 Category table 的 Migration。<br>
Key 的型態為 String,當然也就不是 auto increment。</p>
<pre class=" language-php"><code class="prism language-php"><span class="token variable">$table</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">string</span><span class="token punctuation">(</span><span class="token string">'id'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">primary</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<hr>
<h2 id="解決方法">解決方法</h2>
<p>依據 [Eloquent 文件] 的說明(<a href="https://laravel.com/docs/5.6/eloquent">https://laravel.com/docs/5.6/eloquent</a>):</p>
<blockquote>
<p>If you wish to use a non-incrementing or a non-numeric primary key you must set the public <code>$incrementing</code> property on your model to <code>false</code>. If your primary key is not an integer, you should set the protected <code>$keyType</code> property on your model to <code>string</code>.</p>
</blockquote>
<p>擷取 <a href="https://docs.laravel-dojo.com/laravel/5.5/eloquent">Laravel 道場的中文翻譯</a>:</p>
<blockquote>
<p>如果你希望使用非遞增或非數字的主鍵,務必把模型上的 public <code>$incrementing</code> 屬性設定為 <code>false</code>。如果你的主鍵不是一個整數,你應該將模型上的 protected <code>$keyType</code> 屬性設定為 <code>string</code>。</p>
</blockquote>
<p>所以我們要在 Category 的 Model 中新增下面的程式碼:</p>
<pre class=" language-php"><code class="prism language-php"><span class="token keyword">public</span> <span class="token variable">$incrementing</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token keyword">protected</span> <span class="token variable">$keyType</span> <span class="token operator">=</span> <span class="token string">'string'</span><span class="token punctuation">;</span>
</code></pre>
<p>這樣就可以在儲存 one to many 的時候,抓取到正確的 <code>category_id</code> 資料了!</p>
<p><em>首圖:By Chad250 (Own work), CC BY-SA 4.0, via Wikimedia Commons</em></p>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-27581653183388950092018-02-13T00:25:00.001+08:002018-02-13T00:47:55.114+08:00[筆記] 無法重置 MariaDB 的 root 密碼該怎麼做?<p><img src="https://mariadb.org/wp-content/uploads/2015/10/mariadb-usa-inc.png" alt="enter image description here"><br>
忘記 MariaDB 的密碼想要重設,下了多個常見的指令</p>
<pre class=" language-sql"><code class="prism language-sql">MariaDB <span class="token punctuation">[</span><span class="token punctuation">(</span>none<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token operator">></span> <span class="token keyword">SET</span> PASSWORD <span class="token keyword">FOR</span> <span class="token string">'root'</span>@'localhost<span class="token string">' = PASSWORD('</span>new_password<span class="token string">');
MariaDB [(none)]> ALTER USER '</span>root<span class="token string">'@'</span>localhost<span class="token string">' IDENTIFIED BY '</span>new_password<span class="token string">';
MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('</span>new_password<span class="token string">') WHERE User = '</span>root<span class="token string">' AND Host = '</span>localhost<span class="token string">';
MariaDB [(none)]> update mysql.user set password=password('</span>MyNewPass<span class="token string">') where user='</span>root'<span class="token punctuation">;</span>
</code></pre>
<p>都失敗,出現的錯誤訊息大多如下</p>
<blockquote>
<p>ERROR 1131 (42000): You are using MariaDB as an anonymous user and anonymous users are not allowed to change password</p>
</blockquote>
<p>照著很多網路步驟做,都還是不能修改成功。<br>
都是當下看起來成功,然後下 <code>quit</code> 重新登入又失敗。</p>
<p>這時只好換個關鍵字搜尋:"cant reset MariaDB password"<br>
就被拯救了!</p>
<hr>
<h2 id="發生什麼事?">發生什麼事?</h2>
<p>多篇文章都提到同一個現象<br>
如果你的 <code>mysql.user</code> table 裡面的 <code>plugin</code> 欄位被 “unix_socket” 佔據,<br>
就會修改失敗(這是什麼巫術…)</p>
<p>依據國外網友分析,<br>
通常 MariaDB 更新後, <code>plugin</code> 欄位就會被補上這個值(這又是什麼巫術…)</p>
<p>大致看一下, <a href="https://mariadb.com/kb/en/library/authentication-plugin-unix-socket/">Unix Socket</a> 是一個認證外掛,<br>
主要是讓使用者可以直接透過系統認證進入資料庫,不需輸入密碼。<br>
(那為什麼我還要這麼折騰進不去RRRR)</p>
<hr>
<h2 id="所以解決方法?">所以解決方法?</h2>
<p>解決方法很簡單,就是把 “unix_socket” 清掉…(這…)<br>
其實我也不知道這個做法好不好,<s>但總比進不去好</s><br>
如果有更好的解決方案還請留言告訴我。</p>
<h3 id="先想辦法登進去">1. 先想辦法登進去</h3>
<p>如果你之前有嘗試重設過,記得也要先 <code>kill</code> 所有 mysql/mariadb 的 processes,再開始以下步驟。</p>
<pre class=" language-bash"><code class="prism language-bash">$ <span class="token function">sudo</span> systemctl stop mysql
$ <span class="token function">sudo</span> mysqld_safe --skip-grant-tables --skip-networking <span class="token operator">&</span>
$ mysql -u root
</code></pre>
<h3 id="查看-plugin">2. 查看 plugin</h3>
<p>我們看一下 <code>mysql.user</code> 是不是被 “unix_socket” 了</p>
<pre class=" language-sql"><code class="prism language-sql">MariaDB <span class="token punctuation">[</span><span class="token punctuation">(</span>none<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token operator">></span> <span class="token keyword">SELECT</span> <span class="token keyword">user</span><span class="token punctuation">,</span> plugin <span class="token keyword">FROM</span> mysql<span class="token punctuation">.</span><span class="token keyword">user</span><span class="token punctuation">;</span>
<span class="token operator">+</span><span class="token comment" spellcheck="true">------+-------------+</span>
<span class="token operator">|</span> <span class="token keyword">user</span> <span class="token operator">|</span> plugin <span class="token operator">|</span>
<span class="token operator">+</span><span class="token comment" spellcheck="true">------+-------------+</span>
<span class="token operator">|</span> root <span class="token operator">|</span> unix_socket <span class="token operator">|</span>
<span class="token operator">+</span><span class="token comment" spellcheck="true">------+-------------+</span>
<span class="token number">1</span> <span class="token keyword">row</span> <span class="token operator">in</span> <span class="token keyword">set</span> <span class="token punctuation">(</span><span class="token number">0.00</span> sec<span class="token punctuation">)</span>
</code></pre>
<p>看起來是真的中了。</p>
<h3 id="把它清掉">3. 把它清掉</h3>
<p>這裡不下 <code>WHERE</code> 語法,直接把所有 plugin 清空</p>
<pre class=" language-sql"><code class="prism language-sql">MariaDB <span class="token punctuation">[</span><span class="token punctuation">(</span>none<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token operator">></span> <span class="token keyword">UPDATE</span> mysql<span class="token punctuation">.</span><span class="token keyword">user</span> <span class="token keyword">SET</span> plugin<span class="token operator">=</span><span class="token string">""</span><span class="token punctuation">;</span>
Query OK<span class="token punctuation">,</span> <span class="token number">1</span> <span class="token keyword">row</span> affected <span class="token punctuation">(</span><span class="token number">0.00</span> sec<span class="token punctuation">)</span>
<span class="token keyword">Rows</span> <span class="token keyword">matched</span>: <span class="token number">1</span> Changed: <span class="token number">1</span> <span class="token keyword">Warnings</span>: <span class="token number">0</span>
</code></pre>
<p>確認一下是不是清空了</p>
<pre class=" language-sql"><code class="prism language-sql">MariaDB <span class="token punctuation">[</span><span class="token punctuation">(</span>none<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token operator">></span> <span class="token keyword">SELECT</span> <span class="token keyword">user</span><span class="token punctuation">,</span> plugin <span class="token keyword">FROM</span> mysql<span class="token punctuation">.</span><span class="token keyword">user</span><span class="token punctuation">;</span>
<span class="token operator">+</span><span class="token comment" spellcheck="true">------+--------+</span>
<span class="token operator">|</span> <span class="token keyword">user</span> <span class="token operator">|</span> plugin <span class="token operator">|</span>
<span class="token operator">+</span><span class="token comment" spellcheck="true">------+--------+</span>
<span class="token operator">|</span> root <span class="token operator">|</span> <span class="token operator">|</span>
<span class="token operator">+</span><span class="token comment" spellcheck="true">------+--------+</span>
<span class="token number">1</span> <span class="token keyword">row</span> <span class="token operator">in</span> <span class="token keyword">set</span> <span class="token punctuation">(</span><span class="token number">0.00</span> sec<span class="token punctuation">)</span>
</code></pre>
<h3 id="重設密碼">4. 重設密碼</h3>
<p>終於來到重設密碼的步驟了</p>
<pre class=" language-sql"><code class="prism language-sql">MariaDB <span class="token punctuation">[</span><span class="token punctuation">(</span>none<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token operator">></span> <span class="token keyword">UPDATE</span> mysql<span class="token punctuation">.</span><span class="token keyword">user</span> <span class="token keyword">SET</span> password<span class="token operator">=</span>PASSWORD<span class="token punctuation">(</span><span class="token string">"your_new_password"</span><span class="token punctuation">)</span> <span class="token keyword">WHERE</span> <span class="token keyword">user</span><span class="token operator">=</span><span class="token string">"root"</span><span class="token punctuation">;</span>
Query OK<span class="token punctuation">,</span> <span class="token number">0</span> <span class="token keyword">rows</span> affected <span class="token punctuation">(</span><span class="token number">0.00</span> sec<span class="token punctuation">)</span>
<span class="token keyword">Rows</span> <span class="token keyword">matched</span>: <span class="token number">1</span> Changed: <span class="token number">0</span> <span class="token keyword">Warnings</span>: <span class="token number">0</span>
</code></pre>
<p>離開前讓 MariaDB 重新讀取使用者權限</p>
<pre class=" language-sql"><code class="prism language-sql">MariaDB <span class="token punctuation">[</span><span class="token punctuation">(</span>none<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token operator">></span> FLUSH <span class="token keyword">PRIVILEGES</span><span class="token punctuation">;</span>
Query OK<span class="token punctuation">,</span> <span class="token number">0</span> <span class="token keyword">rows</span> affected <span class="token punctuation">(</span><span class="token number">0.00</span> sec<span class="token punctuation">)</span>
MariaDB <span class="token punctuation">[</span><span class="token punctuation">(</span>none<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token operator">></span> quit
Bye
</code></pre>
<h3 id="完成">5. 完成</h3>
<p>先手動 <code>kill</code> 所有 mysql/mariadb 的 processes<br>
然後重新啟動<br>
試試看可不可以登入</p>
<pre class=" language-bash"><code class="prism language-bash">$ <span class="token function">sudo</span> <span class="token function">kill</span> XXXXXX <span class="token comment" spellcheck="true"># kill 各種 mariadb processes</span>
$ <span class="token function">sudo</span> systemctl start mysql
$ mysql -u root -p
</code></pre>
<p>終於進去了!<br>
大吉大利,晚上吃雞!</p>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://superuser.com/questions/949496/cant-reset-mysql-mariadb-root-password">Can’t reset MySQL (MariaDB) root password</a> - Super User</li>
</ul>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-79059326486632506232018-02-01T19:15:00.001+08:002018-02-01T19:18:00.532+08:00[筆記] 使用其他 User 執行 Cron Job 腳本<p>以 Laravel 的 <code>php artisan schedule</code> 為例,<br>
如果希望是特定使用者執行的話可以這樣做:</p>
<h2 id="crontab-部分">Crontab 部分</h2>
<p>在 crontab 中用 <code>su</code> 指定使用者(例如 www),並執行 shell 腳本。</p>
<p>例如在 <code>crontab -e</code> 中:</p>
<pre class=" language-bash"><code class="prism language-bash">* * * * * <span class="token function">su</span> www -c <span class="token string">"/usr/local/bin/laravel-scheduler.sh"</span>
</code></pre>
<h2 id="shell-script-部分">Shell Script 部分</h2>
<p>使用 crontab 執行 shell script 時,預設不會載入 .bashrc。</p>
<p>因為我們有使用 <a href="https://github.com/phpbrew/phpbrew">PHPBrew</a> 管理多版本的 PHP,<br>
需要手動透過 <code>source</code> 載入環境變數,<br>
才能執行正確的 PHP 版本。</p>
<p>例如在 <code>/usr/local/bin/laravel-scheduler.sh</code> 中:</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token shebang important">#!/bin/bash</span>
<span class="token function">source</span> ~/.bashrc <span class="token comment" spellcheck="true"># 載入 www 的 bash 環境變數</span>
php /path-to-your-project/artisan schedule:run <span class="token operator">>></span> /dev/null 2<span class="token operator">></span><span class="token operator">&</span>1
</code></pre>
<hr>
<h2 id="其實...">其實…</h2>
<p>網路上找到一些其他的解決方案,我還沒實際嘗試,有錯誤的話煩請告訴我。</p>
<h3 id="也可以都寫在-crontab">也可以都寫在 Crontab</h3>
<p>這樣就不用寫 shell script。</p>
<p>例如在 <code>crontab -e</code> 中:</p>
<pre class=" language-bash"><code class="prism language-bash">* * * * * <span class="token function">su</span> www -c <span class="token string">"source /home/www/.bashrc; <command>"</span>
</code></pre>
<h3 id="也可以直接指定-shell-和環境變數">也可以直接指定 Shell 和環境變數</h3>
<p>但這應該會影響到整個 crontab。</p>
<p>例如在 <code>crontab -e</code> 中:</p>
<pre class=" language-bash"><code class="prism language-bash">SHELL<span class="token operator">=</span>/bin/bash
BASH_ENV<span class="token operator">=</span><span class="token string">"/home/www/.bashrc"</span>
* * * * * <span class="token function">su</span> www -c <span class="token string">"<command>"</span>
</code></pre>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-46446956492157897702018-01-24T18:22:00.001+08:002018-02-13T00:59:08.707+08:00[筆記] 不小心 Git push 了含有錯誤內容的 commit 怎麼辦?<p>狀況:<br>
想要修改已經 push 的 commit,<br>
而且很慘的是,剛剛也不小心已經在 Server 上執行 git pull 了,怎麼辦?</p>
<h2 id="步驟一:修改最後一次的-commit">步驟一:修改最後一次的 Commit</h2>
<p>完成如 <code>git add</code> 等 commit 變動後,將 <code>git commit</code> 的步驟加上 <code>--amend</code> 參數,如:</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token punctuation">(</span>local<span class="token punctuation">)</span> $ <span class="token function">git</span> commit --amend --no-edit
</code></pre>
<p>運用此參數,git 就會直接修改最後一次的 commit,而不會增加新的 commit。<br>
這裡我們又加了 <code>--no-edit</code> 參數,就是不修改原本 commit 訊息的意思。</p>
<hr>
<h2 id="步驟二:強制-push-遠端分支">步驟二:強制 push 遠端分支</h2>
<p>接著強制覆寫遠端 git 的分支紀錄</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token punctuation">(</span>local<span class="token punctuation">)</span> $ <span class="token function">git</span> push -f
</code></pre>
<hr>
<h2 id="步驟三:server-強制-pull">步驟三:Server 強制 pull</h2>
<p>因為 Server 留有錯誤的 commit,<s>所以要透過 <code>rebase</code> 來更改。</s><br>
<s><code>(server) $ git pull --rebase</code></s></p>
<p>20190213 更新:不對,比較好的做法是 <code>reset</code>,否則錯誤檔案會留在工作目錄。</p>
<pre class=" language-bash"><code class="prism language-bash"><span class="token punctuation">(</span>server<span class="token punctuation">)</span> $ <span class="token function">git</span> fetch <span class="token comment" spellcheck="true"># 先取得最新的樹</span>
<span class="token punctuation">(</span>server<span class="token punctuation">)</span> $ <span class="token function">git</span> reset origin/master --hard <span class="token comment" spellcheck="true"># 再 hard reset 到正確的 origin/master</span>
</code></pre>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>關於各個指令的詳細資訊,以下資料有更完整的敘述和說明:</p>
<ul>
<li><a href="https://gitbook.tw/chapters/using-git/amend-commit2.html">【狀況題】追加檔案到最近一次的 Commit</a> - 為你自己學 Git</li>
<li><a href="https://gitbook.tw/chapters/github/fail-to-push.html">【狀況題】怎麼有時候推不上去…</a> - 為你自己學 Git</li>
<li><a href="https://gitbook.tw/chapters/github/pull-from-github.html">Pull 下載更新</a> - 為你自己學 Git</li>
<li></li>
</ul>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-66110245292732187132017-12-23T00:11:00.001+08:002017-12-23T11:38:57.362+08:00最方便的程式教室 GitHub Classroom 動手玩<p>GitHub Classroom 推出已經有一段時間,好像沒看到網路上有人介紹他的使用方法。剛好最近有機會可以嘗試看看,就寫個紀錄分享一下 GitHub Classroom 的一些特色功能。</p>
<h2 id="開始使用-github-classroom">開始使用 GitHub Classroom</h2>
<p>首先當然是直接到 GitHub Classroom 開間<code>教室</code>啊,網址如下:<br>
<a href="https://classroom.github.com">https://classroom.github.com</a></p>
<p>過程十分簡單,稍微講一下就好:</p>
<ul>
<li>開<code>教室</code>的人必須要將教室連結到一個 <code>GitHub Organization</code>(下稱<code>組織</code>)</li>
<li>既然是要連到<code>組織</code>,那想當然每個學生都要加到<code>組織</code>中</li>
<li><code>教室</code>可以設定多個 <code>Administrators</code>(下稱<code>管理員</code>),前提是他們必須要有<code>組織</code>的<code>Owner</code>身份</li>
<li>可以把每個學生的 GitHub 帳號對應到自定義的 <code>roster</code> (下稱<code>名單</code>),就不用背帳號名了!!(超棒 🎉🎉)</li>
<li>學生<code>名單</code>是可以隨時變動的</li>
</ul>
<h2 id="設定學生名單">設定學生名單</h2>
<p>GitHub 提供了很方便識別學生帳號的方法,就是類似暱稱的方式將你的名單跟學生們的帳號綁定起來。<br>
<img src="https://i.imgur.com/ztq6PpL.png" alt="enter image description here"></p>
<p>第一格:要你填入你想用什麼方式識別 GitHub 帳號,你可以選擇任意的暱稱來識別 GitHub 帳號,官方舉例是用姓名、email 或學號等。</p>
<p>填這格的目的,是讓其他 <code>管理員</code> 在手動增加新學生的時候,輸入框旁會提示應該填寫什麼樣的內容,以便讓整份名單格式保持一致。因為我的授課對象比較接近社團性質,我在這裡是使用「班級 座號 姓名」混合識別。</p>
<p>第二格:則是填寫每個學生的資料。如果你剛剛選擇使用姓名識別,就是將學生姓名一人一行表列出來。你也可以直接上傳 CSV 檔,學校老師看到這功能應該蠻高興的。</p>
<p>由於<code>名單</code>是可以隨時變動的,所以操作過程有誤隨時都可以砍掉重來。</p>
<p>新增好後會像這樣,目前只有名單但還沒跟 GitHub 帳號連接。這部分要等到學生交作業的時候才能連接。<br>
<img src="https://i.imgur.com/DKfAF8B.png" alt="enter image description here"></p>
<h2 id="建立作業">建立作業</h2>
<p>接著我們就來建立第一份作業。</p>
<h3 id="選擇作業類型">選擇作業類型</h3>
<p>作業分成個人作業和分組作業,這裡以個人作業為例。<br>
<img src="https://i.imgur.com/oul5Pk7.png" alt="enter image description here"></p>
<h3 id="設定作業內容">設定作業內容</h3>
<p>選擇「Create an individual assignment」後,介面就跟一般建立 repo 類似。</p>
<p>在這裡你可以指定:</p>
<ul>
<li>作業名稱</li>
<li>repo 的前綴詞(給學生在自己帳號建立 repo 用)</li>
<li>設定學生 repo 為 public 或 private</li>
<li>是否給予學生自己 repo 的 Admin 權限</li>
<li>指定 repo 的預設 code 內容(可選)</li>
<li>作業繳交期限(可選)</li>
</ul>
<p>需要注意的是,private repo 必須要額外申請教育組織權限才可使用(免費),申請時需要進行教師身分驗證。由於 <s>懶的申請</s> 鼓勵開源文化與信任文化,這裡我們就使用 Public repo 了。<br>
<img src="https://i.imgur.com/Y7Ed6HN.png" alt="enter image description here"></p>
<h3 id="產生作業網址">產生作業網址</h3>
<p>接者就會顯示成功建立的訊息,將作業網址傳送給學生們。<br>
<img src="https://i.imgur.com/DQiKz60.jpg" alt="enter image description here"></p>
<h2 id="學生繳交作業">學生繳交作業</h2>
<p>接著我們從學生角度看看怎麼繳交作業。</p>
<h3 id="從作業連結加入教室">從作業連結加入教室</h3>
<p>學生第一次進入班級的作業連結時,會要求配對自己的帳號到名單中,這樣的設計幫老師省了不少時間(雖然還是要先一個一個邀請進組織⋯⋯)。比較奇怪的是,名單的排序好像跟老師看到的相反。<br>
<img src="https://i.imgur.com/snjsd2u.jpg" alt="enter image description here"></p>
<h3 id="接受作業">接受作業</h3>
<p>帳號配對好對應的名字後,接著就是接受作業。GitHub 會自動在組織中建立一個 repo,名稱包含我們剛剛指定的前綴以及學生的 GitHub 帳號名。<br>
<img src="https://i.imgur.com/Oupxmj6.png" alt="enter image description here"></p>
<h3 id="完成建立-repo">完成建立 repo</h3>
<p>接著就完成了 repo 的建立,學生可以在其專屬的 repo 中上傳作業。<br>
<img src="https://i.imgur.com/5bcBtxA.png" alt="enter image description here"></p>
<p>接著就是基本的 Git/GitHub 操作,這裡就不再贅述。</p>
<h2 id="老師的管理功能">老師的管理功能</h2>
<p>老師在學生作業繳交後,有一些管理的功能可以操作。</p>
<h3 id="自動完成身份連結">自動完成身份連結</h3>
<p>學生一但綁定好帳號,老師就可以在教室的名單頁面中看到,該學生的帳號已經跟名單連結在一起了。<br>
<img src="https://i.imgur.com/TC7bUDJ.png" alt="enter image description here"></p>
<h3 id="查看作業繳交狀況">查看作業繳交狀況</h3>
<p>進到作業的頁面,可以看到現在學生們的作業繳交狀況。<br>
<img src="https://i.imgur.com/yo6Ssg6.jpg" alt="enter image description here"></p>
<h3 id="增加新的學生">增加新的學生</h3>
<p>在名單頁面也可以隨時新增、刪除學生到名單中。<br>
<img src="https://i.imgur.com/vSrl2PA.png" alt="enter image description here"></p>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.comtag:blogger.com,1999:blog-17571305.post-71022980310794085472017-12-21T17:27:00.001+08:002017-12-21T17:27:42.033+08:00[筆記] 用 Graph API 檢查 Instant Articles 錯誤<p>文件竟然什麼都沒寫@@</p>
<h2 id="抓取所有-ia-文章">抓取所有 IA 文章</h2>
<p>這個文件有,可以參考 <a href="https://developers.facebook.com/docs/instant-articles/api#list-all">List All Articles</a>。</p>
<pre><code>https://graph.facebook.com/{page-id}/instant_articles
</code></pre>
<h2 id="抓取所有-ia-文章,並包含狀態">抓取所有 IA 文章,並包含狀態</h2>
<p>這是我從 <a href="https://developers.facebook.com/tools/explorer/">Graph API Explorer</a> 挖出來的。重點是 <code>most_recent_import_status</code> 這個 field 竟然沒有在文件中出現…</p>
<pre><code>https://graph.facebook.com/{page-id}/instant_articles?fields=most_recent_import_status
</code></pre>
<h2 id="其他實用的-field">其他實用的 field</h2>
<ul>
<li><code>limit</code></li>
<li><code>publish_status</code></li>
</ul>
小克http://www.blogger.com/profile/11621593992969041904noreply@blogger.com