將鐵人賽的文章搬到部落格

好久沒有寫文章了,清清一下灰塵~

不過眼尖的會發現,目前我的部落格裡面也包含了前兩次我參加it鐵人賽的文章。因為後來我起了一個python爬蟲爬我自己鐵人賽的文章,然後發出request給自己的部落格新增文章。不過在之前要啟用一下自己的部落格可以開放網路發request用,所以要安裝一個package:Application Passwords plugin

詳情的話可以參考這篇文章:https://www.yannyann.com/2018/09/wp-rest-api-create-new-post-and-upload-image/

然後我的相關程式碼放到我的office 那裡了:https://github.com/r567tw/office/tree/master/ItHomeToTechBlog

小君曰:完成今年一大目標,打勾✔️

 

 

賽後小感想以及後續學習

終於來到這最後一天,然而人家最後一天都在寫些感言充廢文,我在這一天還是要稍微帶點技術含量的東西ㄎㄎ

以下東西很多又很雜,畢竟 php 就是義大利麵嘛(大誤),請耐心閱讀~

Laravel Best Practice

介紹一個 Github 專案:https://github.com/alexeymezenin/laravel-best-practices

裡面介紹很多建議的 Laravel 寫法,例如驗證不要寫在 controller 裡面,而是用 Request 類別作為包裝,在寫 Laravel 的時候可以根據這些原則檢核一下自己

Laravel 遇上大架構

當 Laravel 遇到大架構的時候,基本上我們不會把這些東西都只是塞在 Model\Controller\view 當中,而是會使用到 Repository、Service、Presenter 或 Transformer 做包裝,分別控制資料庫邏輯與商業邏輯、顯示的邏輯和格式的回傳,讓程式更加容易維護、易讀

大架構的部分說明你可以參考以下網址:

可以讓 PHP 偉大的其他東西

其實只學會 Laravel 不足以讓 PHP 偉大啦,不過我期許自己是能夠成為越來越強的 phper 的喔!希望你們也是^^

1.學習 swoole:據說這是可以讓 php 效能 up up 的工具框架,是現代 phper 值得學習的一項東西,也是可以讓 php 邁向異步時代的重要推手

2.學習 composer:我還能說什麼呢?沒有 Composer 別跟我說他是現代 php 框架 XD

2.學習 lumen、slim: 剛剛學過 Laravel 一遍了,他就是這麼的肥這麼的胖,所以如果能學會幾個微框架是不錯的,對應 Laravel 來說,Lumen 就是他的簡易版,相信學會了 Laravel 以後,Lumen 上手應該不是什麼太大的難事。

如何學好 php

其實我覺得網路上的資源太多了,容易眼花撩亂而且有時候還會學到舊的。我個人是建議以下這些資料

我個人只推薦這幾個資源,其他就不必了。因為這些資源是可以讓你比較能夠學習現代php的方法與資源。

Laravel 相關資源

  • larvel news: 每週都會寄給你 Laravel 界相關資訊,你也可以加入他的 Procast,順便練習英文聽力
  • Diving Laravel:一個深入 Laravel 核心的部落格,會講到 Queue、job 等等較為進階的功能與核心解釋等。

賽後感言

其實一開始還卡在題目到底要選什麼…然後就咚咚咚的到開賽前一刻才決定好,於是成為了時間驅動寫作,也因為自己的惰性使然,其實有點虎頭蛇尾,自己都覺得自己好像沒有寫的太好……。只能說又學到這次的經驗了~

然後有別於去年我寫的 python,這次的 Laravel 似乎不怎麼討好…所以後面也越寫越沒勁兒,於是成為這樣小蛇尾,我自己應該好好檢討吧 XD

本人的技術部落格:https://tech.r567tw.tw/
歡迎使用 feedly 訂閱我的網站喔 XD

未來我會慢慢的將我鐵人賽的文章慢慢的搬過去我個人網站的,並且加以擴充、更新吧?!應該吧?!

Laravel 套件

今天將帶大家快速帶過幾個官方套件以及個人工作經驗上覺得好用的套件。並且後續也給大家相關的軍火庫可以在日後開發專案上用到。基本上有相對應的需求才用,可以搭配該套件的官方文件撰寫程式,這些基本上文件都很易讀,相信無痛上手是很有可能的喔!

官方套件 篇

首先我一定要先推薦一下 Laravel 官方提供的套件啦,就是這些套件形成 Laravel 一個龐大且厲害的生態系。

  1. Laravel Cashier(官方文件):一個關於金流的套件,他可以與國外金流公司 API 做無痛的結合,例如 Stripe 或者 Braintree,當然,如果台灣的話可以使用其他的套件,例如laravel-newebpay或者laravel-payum
  2. Laravel Dusk(官方文件):還記得我們之前的測試篇嗎?其實 Dusk 這個服務有點像是Browsers的測試,如果你看到文件你就大概明瞭,他是有點 for 終端測試的角色
  3. Laravel Passport(官方文件):一個快速建立 API 授權請求的相關套件,基於 Oauth2 標準
  4. Laravel Scout(官方文件): 一個基於 Eloquent Model 所建立的全文搜索相關開發套件,並且預設以 Algolia 作為驅動
  5. Laravel Socialite(官方文件):我們在網頁註冊的時候,常常看到 FB/Google 一鍵登入對吧?其實實作 Facebook,Twitter,LinkedIn,Google,GitHub,GitLab 和 Bitbucket 等等相關身份驗證機制並不難,這個套件可以提供你這樣的功能~
  6. Laravel Telescope(官方文件):本人認為史上最牛的開發調試工具,可以觀察資料庫、也可以觀察任務工作、Request/Respose 等等

另外還有很多其他的官方套件,不過我覺得很少用所以就不特別介紹了……

個人經驗 篇

  1. laravel-excel(官方文件): 一個可以方便操作 Excel 的套件
  1. laravel log viewer(官方文件):有時候我們會需要 Trace log 好幫助我們能夠 trace Request 或者 Response 喔喔~
  2. laravel-cors(官方文件):前後端分離,你會遇到的 Cors 問題~
  3. laravel-permission(官方文件): Laravel 界最有名的權限/角色管理套件
  4. laratrust(官方文件): 另一套個人覺得也沒好用的權限/角色管理套件,而且他比前面的 Laravel-permission 多支援 Group 的特色,好用!然後文件也寫得和 Laravel 易讀好用。
  5. Forms & HTML(官方文件): Laravel 在 4.x 的版本有所謂的 Form 的語法糖,但在 5.x 版本之後便移除了,很多人還想要繼續有這種功能,所以這個套件便出現了啦!

軍火庫

接下來,我要介紹一下 Laravel 的軍火庫

  • packagist :Laravel 使用 Composer,而 composer 使用的軍火庫也就是這個!
  • packalyst:類似上面的 packagist,不過這是專屬 Laravel 的喔!

Laravel: 遺珠之憾

剩下最後的三天鐵人賽,其實原本我有點想繼續寫下去的…但說真的有點有氣無力,如果你發現我最近這幾天的文章風格與教學,就可以發現我其實有點虎頭蛇尾了哈哈。

所以最後這三天將進入第三階段新的章節,也就是主要會再討論關於 Laravel 的套件、以及大架構、還有 Best Practice 等等的內容,雖然技術含量不高,但也就是我基於我所有的 Laravel 經驗全力輸出了!

不過我個人是還蠻喜歡看書的,在資訊界的領域當中,歐萊禮是很多人常常入門的資訊書出版社。所以這裡,我要學習歐萊禮的寫作風格,寫寫一些遺珠之憾,好讓大家不至於感覺有點一半跑掉,而是後續還能有些內容學習和追蹤。

  • Notifications
    Laravel 有一個類別是 Notifications,有別於我們之前寫到的 mailable 可以寄信,Notifications 可以寄送通知到其他服務,例如 Slack、簡訊、或者其他類似可以收取資訊的內容
  • Queues
    這個東西允許你將一個比較耗時的任務延後處理,好讓你的網頁服務不至於為了處理某些很複雜的請求讓後面在等待的其他請求全部 pedding,無法更快的反應。而他的背後又可以與 Redis 或者 Amazon SQS 等服務一起工作
  • Cache
    Laravel 是個全能型的框架,所以其實他的速度會很,所以 Laravel 允許你快取一些設定檔或者路由,好讓整個網頁的效能可以做個簡易的提升,像是php artisan config:cache或者php artisan route:cache之類的指令都可以做這樣的快取。
  • Frontend
    Laravel 的前端框架預設是Vue,然而,現在他也允許使用React了,在我們目錄底下有一個檔案是webpack.mix.js,還記得我們之前使用Auth的時候就有用到類似npm run build之類的指令嗎,其實就是動用這個檔案裡面的設定與調整。以及 resourse 裡面的 js 檔如何做出編譯也是這部分的課題。
  • Broadcasting(廣播)
    此類課題有點困難,我自己也私自下班玩過一次而已,這個東西其實需要配合前端以及相對應的套件,其中一個用途就是可以寫聊天室、或者與 Websocket 的結合,讓網頁也可以主動通知使用者
  • Event
    它提供了一個簡單的監聽器實作,允許你在應用程式可以訂閱和監聽事件
  • Laravel 及 Redis 相互搭配使用

明天我將分享一些生態系的官方套件以及自己之前工作經驗常用到的套件。明天見囉!

Laravel Collections

接下來,我想要分享關於Laravel 的一個比較特別的類別:Collections
他有點像是陣列的概念,但更像是一個集合的概念。

相信如果你還記得前面教學談到幾行的程式碼,裡面不是有Article::all$article->tags這幾段嗎?如果你去dd()它,你會發現他們都是同一種類別:Illuminate\Database\Eloquent\Collection

當然,如果你有在之前helper的章節發現到collect()這個方法,他其實回傳的也是Collection,但是他是Illuminate\Support\Collection

兩者在使用上會有一些差異,基本上他們也是大部分使用上也蠻像的,所以我就在這裡把他們放在一起講。

兩者使用差異可以參考這篇文章:https://medium.com/@lynnlin827/two-types-of-collections-in-laravel-888d43858c4e

Laravel為Collection這個類別提供許多的方法,例如map()或者avg()等等,其實和helper()那裏一樣,其實文件也大部分都寫的清清楚楚了。
https://laravel.com/docs/6.x/collections#available-methods

是不是有點像是在寫JavaScript在處理呢!沒錯,我也有這樣的感覺。但這樣似乎程式變得更好讀了呢!

另外Laravel在6.0也推出Lazy Collections的類別,是為了處理我們有時候會有大量的資料時候,避免一次全部讀進記憶體,使用到比較現代化的PHP技巧:yield 和 generator,而開發的一個新類別。其實使用方法很簡單,就是將我們原本使用的all()改成cursor()就完成囉!

Laravel 多語系網站

接著前一天的Helper主題,我們那時談到了trans()這個helper,底下應該會常常在使用它,我們有時候會需要有國際化的需求,需要征服宇宙,所以做一個多語系的網站是有必要的。接下來,讓我們來示範一下Laravel這個全能型框架關於多語系能力的展現吧!

網頁要實現所謂的多語系有兩種方法

  1. 建立多個網站(土法煉鋼型):例如我需要有英文和中文的網站,就會英文有一個網站,中文又會有另外一個網站。這兩個網站的樣子可能會有很大的不一樣或者很大的一模一樣,好處是:可以就語系別做出更高的客製化,然後壞處就是:費工,重複造的輪子肯定很多
  2. 使用語系檔將檔案內容作出轉換:以上面的例子來說,可能我會有一個中文的語系檔和另外一個英文的語系檔,根據網站的語系選擇呈現以某個語系檔所轉換過來的文字。好處是:重複造的輪子不多,只要學會翻譯文字就好,壞處就是無法提供給各語系特別的客製化

我大概能想到的優點和缺點就是這樣,如果有大大可以補充歡迎告訴我。
接下來我們將示範說明的會是第二類。使用所謂的語系檔轉換內容。

Laravel 語系檔存放在resources/lang資料夾的檔案裡。在此資料夾內,一個蘿蔔一個坑,一個網站支援的語系對應到一個語系子目錄。像是這樣的結構:

/resources
    /lang
        /en
            messages.php
        /zh-Hans-TW
            messages.php

語系檔裡面放的到底是什麼,其實就是大概這樣而已拉

<?php

return [
    'welcome' => 'Welcome to our application'
];

一般來說,網站的預設語系是在config/app.php這個檔案裡面有一個locale的屬性

   'locale' => 'en',

然而如果我們想要切換語系的話該怎麼辦?其實很簡單,使用App::setLocale($locale);這個方法就可以了,以下讓我們做個簡單的火力展示。

首先是增加個Route規則

Route::get('welcome/{locale}', function ($locale) {
    app()->setLocale($locale);
    return view('welcome');
});

然後分別建立兩個檔案,一個是resources/lang/zh-TW/message.php以及resources/lang/en/message.php,分別在裡面寫這些:

//zh-TW/message.php
<?php

return [
    'welcome' => '歡迎來到我們的應用程式'
];

//en/message.php
<?php

return [
    'welcome' => 'Welcome to our application'
];

之後到welcome.blade.php修改一下,把原本的Laravel那一行替換成

{{ trans('message.welcome') }}

之後到welcome/zh-twwelcome/en就會看到我們這些被轉換的文字呢!

當然拉,這個超級陽春的多語系,其實我們可以利用cookiesession存放語系值,藉由前面我們說到的middleware,加上app()->setLocale($locale)來做我們語系上的設定喔。

以下是參考的文件:

今天得程式碼不多,要看完整程式碼的可以參考這裡:
https://github.com/r567tw/Make-PHP-Great-Again/commit/9957deac8788ee8bd20ccb3a0cd8ce867e352f0e

Laravel Helpers

寫到這裡,終於剩下最後的5天就可以完成這整個鐵人賽!(撒花

接下來希望自己再接再厲。繼續完成後續幾天的Laravel 教學系列。
今天也是個簡單風(好幾天都是簡單風了QQ)

介紹一下Laravel作為一個全能型的框架,還提供了一些稱為「Helper」的東西幫助我們可以整理程式碼、封裝了一些我們常常會弄到的部分,也可以稱之為「語法糖」,總之幫助我們可以避免「重複造輪子」。而順道一提的是:我們之前也早已用過這些東西了:例如route()view()或者factory()

如果你看到文件:https://laravel.com/docs/master/helpers

其實大概這些語法可以分類為以下幾種:

  1. 陣列及物件類:可以處理陣列與物件的資料等,例如:Arr::add()Arr::where()
  2. 路徑類:就是Laravel一些資料夾的路徑,像是public_pathstorage_path()
  3. 字串類:可以處理我們的字串,Str::cameltrans(),預告一下明天將會討論關於多語系的網站設計,trans()便是我們到時會可能會用到的方法~
  4. 網址類:像是我們之前會用到的route()方法、以及asset()方法都是回傳一串的網址。
  5. 其他:像是無法歸類以上四類的,之前我們用到的view()或者factory()都在這個裡面,而這裡我也順道介紹一個我們在debug常常會用到的dd()

在你任何想要的地方,如果你想知道這個值到底傳出來是怎麼樣的值、怎麼樣的型態,你可以使用dd(),例如我們昨天的分頁:

dd(Article::paginate(5))

當執行到dd()的時候會立刻終止那一段程式,並且var_dump回傳被dd的值與型態,像是這樣
https://i0.wp.com/ithelp.ithome.com.tw/upload/images/20191010/20106999CVvC4sVxvE.png?w=840&ssl=1

我覺得Laravel的文件是世界上最好讀的文件,我想剩下的你們應該可以自行探索吧XD

Laravel Pagination

沒想到寫這麼多天以來,我居然漏掉一個這麼重要的環節:「分頁」。在網頁的世界中,讓資料作出排序及分頁絕對是必要做到的一件事情,Laravel作為一個全能框架,這項功能肯定是有放在裡面的。讓我們以文章列表作為範例,其實就是將原本的Article::all()改為Article::paginate(5)就好囉,至於那個5看你想換哪個數字都可以,那個意思是你要幾個項目為一頁
https://i0.wp.com/ithelp.ithome.com.tw/upload/images/20191009/20106999Z6OiqOOMo9.png?w=840&ssl=1

然而我知道,你會想問如果底下沒有一個可以點連結的頁數頁尾那怎麼行,超簡單,在article/index.blade.php這個檔案下面放至一行程式碼

{{ $articles->links() }}

這樣你就看到囉
https://i2.wp.com/ithelp.ithome.com.tw/upload/images/20191009/20106999STow0Pnjq3.png?w=840&ssl=1

這樣這個分頁是不是很簡單?當然,如果你想要客製化,可以使用這個指令

php artisan vendor:publish --tag=laravel-pagination

你會在views/vendor/pagination裡面看到很多的類似樣板的分頁檔案,bootstrap-4.blade.php是預設的分頁樣板,如果你想要客製化,可以修改那個檔案!
https://i2.wp.com/ithelp.ithome.com.tw/upload/images/20191009/20106999x8iTwr5lyz.png?w=840&ssl=1

當然既然給了我們這麼多的分頁樣板,當然可以用其他的囉,讓我們來修改一下預設的分頁樣板。
來到app/Providers/AppServiceProvider.php這個檔案,use Paginator,之後在boot裡面加入Paginator::defaultView('vendor.pagination.semantic-ui');就可以從bootstrap-4.blade.php改成semantic-ui.blade.php

use Illuminate\Pagination\Paginator;
...(略)
public function boot()
{
    Paginator::defaultView('vendor.pagination.semantic-ui');
}

另外補充一下:如果你想只是有上一頁或下一頁的連結的話,可以使用simplePaginate這個方式。然後按照上面的方式去客製化你的分頁按鈕,他只是在檔案加入simple的前綴而已。

想要更多研究可以看此文件:https://laravel.com/docs/6.x/pagination

要看完整程式碼的可以參考這裡:https://github.com/r567tw/Make-PHP-Great-Again/commit/f3ab7fb7816555dce91df98ea242114b4f477a2e

Laravel Task Scheduling

接下來我想要分享一些關於 CronJob 的一些事情,有時候,我們會有一些日常的工作需要在每天某一個時間固定執行,像是網頁世界最常見的就是發電子報,這只是最常見的例子,當然還有其他類似的例子,例如會員卡收費清Log之類的,我們可以透過 Laravel 的 Task Scheduling 來做這些事情,管你想新增多少項、要做什麼,只要能用程式寫得出來,都可以來做喔!

還記得我們前幾天談到的Laravel Artisan 以及 Command嗎?當然,寫 Task Schedule 可以有很多種不同的方式,但我個人建議是使用先建立 Command 的方式可以讓我們比較好的管理程式碼。當然,其他方法我也會分享的喔~

把 Laravel 加入 CronJob

不知道什麼是 Cron Job 嗎?我剛剛都解釋那麼清楚了:就是一到某個時間,電腦(如果還是開啟的狀態下)就會執行的工作。相對於我們網頁必須要透過request才能發動response的情況下,Cron Job主動多了。

如果你是 Mac 或者 Linux 系統,可以使用這個指令,如果你是 windows 的話,建議你換一個作業系統,開玩笑的,是可以參考網路上說在 windows 建立 cron job 的流程。

$ crontab -e

這個指令會讓你進入到一個vim的模式,你可以參考即將失傳的古老技藝 Vim,總之我們先按一個i按鍵,加入底下這行

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

path-to-your-project只是個例子,請使用你所在的 Laravel 的主目錄
這個部分就是告訴電腦每分鐘都到我那個Laravel專案的目錄,執行php artisan schedule:run 這個指令

貼完那一行之後,你可以按esc,之後使用:wq,離開vim的模式。
之後你會看到這個結果就表示你成功了啦

crontab: installing new crontab

https://i1.wp.com/ithelp.ithome.com.tw/upload/images/20191008/20106999mFLGgm4RvV.png?w=840&ssl=1

定義要被 schedule 的指令或流程

該在哪裡定義 schedule 呢?其實很簡單喔,就是在app/Console/Kernel.php這個檔案裡啦,你會發現有一段被註解的

        // $schedule->command('inspire')
        //          ->hourly();

沒錯喔,他的意思就是每一個小時執行inspire這個指令,也就是php artisan inspire

hourly可以替換成daily(),也能替換成weekdays之類的,更可以輸出到某一個檔案或寄信,像是appendOutputToemailOutputTo
你可以參考這個文件:https://laravel.com/docs/6.x/scheduling#schedule-frequency-options

這也就是為什麼我要說這部分也和artisan 和 command有所關係,把前面的php artisan 去掉就可以加入 command 的參數作為執行。

為了方便,我們可以改成

$schedule->command('inspire')
        ->everyMinute()
        ->appendOutputTo('inspire.log');

之後你就會看到 laravel 專案有一個inspire.log,裡面每一分鐘就會增加一句 inspire 的話語呢!

當然除了command的方式,你也可以使用其它方式呢~讓我做個簡單的示範

        $schedule->call(function () {
            file_put_contents('time.log', \Carbon\Carbon::now());
        })->everyMinute();

這個例子就是每分鐘將每一次時間記錄在time.log

如果有想要學習更多可以看看這個:https://laravel.com/docs/6.x/scheduling

要看完整程式碼的可以參考這裡:https://github.com/r567tw/Make-PHP-Great-Again/commit/49f53d4e3f6b2b89883c905d7f55248a627c910c

Laravel File Storage

接下來我分享關於Laravel內建關於檔案管理的部分,先上個文件:https://laravel.com/docs/6.x/filesystem

Laravel 的檔案除了傳統上的可以存在本機(Server)上,也可以存在AWS S3和ftp/sftp喔,也可以配合一些大大寫好的套件存在GCP或者其他儲存的載體中。

這裡就簡單說明一下:

  • 如果你要提取檔案
$contents = Storage::get('file.jpg');
  • 如果你想要從AWS S3取得檔案的話,在中間加入一個disk即可
$contents = Storage::disk('s3')->get('file.jpg');

註:ftp 和sftp以此類推…

而在文件當中也說到我們可以去強迫他人下載檔案,大家有印象的話通常我們在某些連結點擊時不是就會出現一個跳窗(問你要存在哪裡?)(沒有的話可能是你已經先做預設的調整了~),在Laravel可以這樣用:

return Storage::download('file.jpg');

接下來,我決定帶大家試做一遍,一個極簡單可以上傳檔案的表單。好讓大家試試看這個File Storage的API。

建立Route、Controller和blade

首先,我們先準備兩個route,一個是上傳檔案的form,另外一個則是負責上傳流程的。

// in routes/web.php
Route::get('upload','UploadController@uploadPage');
Route::post('upload','UploadController@upload');

然後建立一下UploadController,你可以用指令,也可以手動建立

$ php artisan make:controller uploadController

然後,我們先來處理form的部分,uploadPage裡面就只是簡單回傳一個view

    public function uploadPage(){
        return view('fileUpload');
    }

別忘了要到resources/view資料夾建立fileUpload.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="row justify-content-center">
                    @if (isset($message))
                        <div class="success">
                            {{ $message }}
                        </div>
                    @endif
                    <form class="form" action="" method="POST" enctype="multipart/form-data">
                        @csrf
                        <div class="form-group">
                            <label for="">檔案上傳</label>
                            <input  class="form-control-file" type="file" name="ImageFile" id="" accept="image/*"/>
                        </div>
                        <button class="btn btn-primary" type="submit">送出</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

請記得,上傳檔案的form必須要加一個enctype="multipart/form-data,否則你無法上傳檔案。

接下來,讓我們開始處理檔案上傳的流程吧!
UploadFileController這個檔案新增upload這個function,別忘了要在之前use Storage

use Storage;
...(略)

    public function upload(Request $request){
        Storage::put('test.jpg', $request->file('ImageFile')->get());
        return view('fileUpload')->withMessage('Success Upload');
    }

說明:這裡可以使用put這個方法,並且將檔案的內容傳入,其實就是這麼簡單,使用request類別的file方法,裡面則就放在表單裡宣告的名稱(name),這裏以ImageFile為例。

<input  class="form-control-file" type="file" name="ImageFile" id="" accept="image/*"/>
對上
$request->file('ImageFile')->get()**

接下來嘗試上傳一個檔案,看到Success Upload就正確囉
https://i0.wp.com/ithelp.ithome.com.tw/upload/images/20191007/20106999BsOfkKRHno.png?w=840&ssl=1

之後你也會看到storage/app 出現了一個test.jpg的檔案囉~
https://i1.wp.com/ithelp.ithome.com.tw/upload/images/20191007/20106999GUp21f1mpL.png?w=840&ssl=1

要看完整程式碼的可以參考這裡:https://github.com/r567tw/Make-PHP-Great-Again/commit/85ba735bbf1144c9c4ea7d5aae142441d59f7871