You are here
Home > !Laravel

turbolinksで画面の表示をスピードアップ (2) <script>

前回に続いて、turbolinksの話。

今回は、turbolinksをもとで、ページに含まれる<script>がどう実行されるか説明します。

前回と同様に以下の2つのファイルを用意します。

<html lang="ja">                                                                                                                                                                                                   
  <head>                                                                                                                                                                                                           
    <meta charset="utf-8">                                                                                                                                                                                         
    <title>Turbolinks</title>                                                                                                                                                                                      
    <script src="https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.0.3/turbolinks.js"></script>                                                                                                                  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>                                                                                                                      
    <script>                                                                                                                                                                                                       
    $(document).on('turbolinks:load', function() {                                                                                                                                                                 
      $("#button").click(function(){                                                                                                                                                                               
        $("#box").animate({left: '250px'});                                                                                                                                                                        
      });                                                                                                                                                                                                          
    });                                                                                                                                                                                                            
    </script>                                                                                                                                                                                                      
  </head>                                                                                                                                                                                                          
  <body>                                                                                                                                                                                                           
    <h1>Turbolinks</h1>                                                                                                                                                                                            
    <ul>                                                                                                                                                                                                           
      <li><a href="page1.html">ページ 1</a></li>                                                                                                                                                                   
    </ul>                                                                                                                                                                                                          
                                                                                                                                                                                                                   
    <button id="button">箱を動かす</button>                                                                                                                                                                        
    <div id="box" style="background:#98bf21;height:100px;width:100px;position:absolute;"></div>                                                                                                                    
  </body>                                                                                                                                                                                                          
</html>                                                                                                                                                                                                            

CDNでturbolinksとjqueryを読み込んでいるところ注意してください。

html lang="ja">                                                                                                                                                                                                   
  <head>                                                                                                                                                                                                           
    <meta charset="utf-8">                                                                                                                                                                                         
    <title>ページ 1</title>                                                                                                                                                                                        
    <script>                                                                                                                                                                                                       
    $(document).ready(function() {                                                                                                                                                                                 
      $("#button2").click(function(){                                                                                                                                                                              
        $("#box").animate({left: '10px'});                                                                                                                                                                         
      });                                                                                                                                                                                                          
    });                                                                                                                                                                                                            
    </script>                                                                                                                                                                                                      
                                                                                                                                                                                                                   
  </head>                                                                                                                                                                                                          
  <body>                                                                                                                                                                                                           
    <h1>ページ 1</h1>                                                                                                                                                                                              
    <p><a href="index.html">戻る</a></p>                                                                                                                                                                           
                                                                                                                                                                                                                   
    <button id="button">箱を動かす</button> <button id="button2">箱を戻す</button>                                                                                                                                 
    <div id="box" style="background:#98bf21;height:100px;width:100px;position:absolute;"></div>                                                                                                                    
  </body>                                                                                                                                                                                                          
</html>  

最初のページで、「箱を動かす」のボタンをクリックすると、箱が右へ移動します。

「ページ1」をクリックして、ページ1に行くと今度も同じく「箱を動かす」のボタンをクリックすると、箱が右へ移動します。このjqueryのコードがページ1(page1.html)になくても。

以下にアップしたので、そこで体験できます。

https://larajapan.lotsofbytes.com/turbolinks/2/index.html

図1

ここで大事なのは、通常なら、

$(document).ready(function() {
..
});

とするところ、

$(document).on('turbolinks:load', function() { 
..
});

とします。window.onloadやjQueryのreadyは、index.htmlがロードしたときにのみ実行されますが、ページ1のリンクのクリックでページ1をajaxで読み込むときには実行されません。ゆえに、ページ1では「箱を動かす」ボタンをクリックしても箱は移動しません。

一方、turbolinks:loadを使用すると、リンク先のページにjavascriptのコードがなくとも、index.htmlのコードを実行します。

さらに、turbolinksは、リンク先のファイル(ここではpage1.html)に、現在のファイル(ここではindex.html)に存在しない、<script;>のコードがあるなら、それもロードしてくれます。それゆえに、「箱を動かす」のボタンをクリックすると、箱が右へ移動しまし、「箱を戻す」をクリックすると、箱は左に移動します。

最後に、今回の例はturbolinksの機能の説明のために、このようなjavascriptのコードとなること理解してください。実際にはpage1.htmlがブックマークされていて、次回のアクセスがそこから始まるなら現在のpage1.htmlのコードでは困ります。つまり、page1.html自体が更新されても同様な動作となる必要があります。

turbolinksで画面の表示をスピードアップ (1)

Laravelのフレームワークのおかげで、自分で作成した古いフレームワークもどきや、CodeIgniterの「もうサポートしません」(注1)フレームワークを脱出できて、以前よりしっかりした開発の領域に入ってきたと感じているこの頃。そして、ファサード、ネームスペース、クロージャ、トレイトなどを活用して、とてもモダン。しかし、最近人気が出てきたJavascriptのフレームワーク、Angular, React, Vuejsを使用したシングルページアプリ(SPA)がとても気になります。

Laravelは基本的にサーバーサイドで、Angularなどはクライアントサイドなので、共存は可能と言えばそうなのだけれど、せっかく時間かけてマスターしたLaravelのプログラムを書き直すとか、今度はどのJavascriptフレームワークをマスターすればよいのとか、ajaxばかりでプログラム複雑になるのでは、とか、過去には、CodeIgniterで苦い目にあったし、最近やっと大きいプロジェクトを3年かけてLaravelに書き直したばかりなので、ちょっとポジティブにはなれません。

そんな中で、知り合ったのが題名のturbolinks。このテクノロジーの謳い文句は、

Javascriptのフレームワークを使用して複雑にすることなしに、シングルページアプリ(SPA)のパフォーマンスが得られる!

なんか「何もしなくても痩せる!」というダイエットサプリメントのような感じですが、今の私にピッタリ。とりあえず、紹介しましょう。

まず、メカニズムですが、そう難しくはありません。ページ内に同じサイト内のリンク、つまり<a href= ..>があると、turbolinksはajaxでそのページを取ってきてその中の<body>のデータを現在のと取り換えます。<head>は同じなので、結果的には、

のように、タブのところに表示される「ページロード中」のぐるぐるがなくなります。さらに、<title>やURLも正しく変わります。

インストールはいたって簡単。

以下の2つのファイルを用意してください。最初のファイルだけに、turbolinks.jsが入っていることに注意してください。

<html lang="ja">                                                                                                                                                                                                   
  <head>                                                                                                                                                                                                           
    <meta charset="utf-8">                                                                                                                                                                                         
    <title>Turbolinks</title>                                                                                                                                                                                      
    <script src="https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.0.3/turbolinks.js"></script>                                                                                                                  
  </head>                                                                                                                                                                                                          
  <body>                                                                                                                                                                                                           
    <h1>Turbolinks</h1>                                                                                                                                                                                            
    <ul>                                                                                                                                                                                                           
      <li><a href="page1.html">ページ 1</a></li>                                                                                                                                                                       
    </ul>                                                                                                                                                                                                          
  </body>                                                                                                                                                                                                          
</html>                                                                                                                                                                                                            
<html lang="ja">                                                                                                                                                                                                   
  <head>                                                                                                                                                                                                           
    <meta charset="utf-8">                                                                                                                                                                                         
    <title>ページ 1</title>                                                                                                                                                                                        
  </head>                                                                                                                                                                                                          
  <body>                                                                                                                                                                                                           
    <h1>ページ 1</h1>                                                                                                                                                                                              
    <p><a href="index.html">戻る</a></p>                                                                                                                                                                               
  </body>                                                                                                                                                                                                          
</html>                                                                                                                                                                                                            

これらのファイルをサーバーにアップして、「ページ1」や「戻る」リンクをクリックしてください。タブの部分において、もうロード中のぐるぐるは見えませんね。「すっ」とページが変わる感じです。また、URLやタイトルも正しく更新されています。

しかし、ページ1においてブラウザの更新ボタンを押して画面を更新した後に、「戻る」のリンクをクリックするときは、ロード中のぐるぐるが見えます。これはpage1.htmlには、turbolinksがないからです。

以下のデモでも体験できます。

https://larajapan.lotsofbytes.com/turbolinks/index.html

ブラウザのインスペクトツールでも、ajaxが使用されていることがわかります。


index.htmlのturbolinksをコールしているscriptの行を削除して違いも見てください。

CDNではなく、turbolinksのファイルが欲しいなら、

$ wget https://github.com/turbolinks/turbolinks/archive/master.zip

で取得可能です。unzipしてから、dist/turbolinks.jsのファイルが取り出せます。

知っておくこととして、

ページの特定のリンクにおいてturbolinksを無効にしたいなら、data-turbolinks="false"を入れてください。

..
  <body>                                                                                                                                                                                                           
    <h1>Turbolinks</h1>                                                                                                                                                                                            
    <ul>                                                                                                                                                                                                           
      <li><a href="page1.html" data-turbolinks="false">ページ 1</li>                                                                                                                                                                       
    </ul>                                                                                                                                                                                                          
  </body>   
..

クリック先のページを読み直したいときとかに必要です。私の経験では、ページを変更してセッションに値を入れるときには、これが必要でした。期待した動作にならないとか問題があるときに試してみること必要です。それから、hrefのリンクのクリックはGETのアクションとなりますが、POSTのアクションには、turbolinksは関与しないので、フォームの投稿はスピードアップはしません。通常の画面の更新となります。

実際のプロジェクトで使用となると、他にも知る必要なことがいくつか(たくさんではない)あります。例えば、Google Analyticsは正しく反映されるのか、とか、Wordpressでも使用できるかとか。知識を整理して、将来により情報を共有します。

注1

CodeIgniterは私が初めて使用したPHP言語のフレームワークです。どこかのサイトで紹介されていて簡単そうなので使い始めました。日本語のデータ処理に問題があったので使える部分はほとんどコントローラの部分だけでしたが、いくつかのお客さんのプロジェクトの開発に使いました。当時は一番人気のフレームワークでしたが、その絶頂期の2013年にCodeIgniterを開発した会社がリソースがないことを理由に開発を辞める宣言をしました。そこで開発が止まり1年後にはカナダの専門学校がメンテナーとなり現在はそこでオープンソースとして管理されています。

ということで、CodeIgniterはまったく消え去ったわけではありませんが、Laravelを使い始めて振り返ると、CodeIgniter自体のフレームワークはその時点ですでに古く、現在のPHP言語のネームスペースに基づくcomposerのパッケージを基本として作成されたものでもなく、より複雑になるウェブアプリの開発にはそのままでは不可能です。

phanは楽しい!(2) laravel編

前回紹介した楽しい開発ツールphanを、今回はLaravelのプロジェクトに適用してみます。さて、どれだけ楽しくなるか!

まず、Laravel 5.4の私の日本語開発用のレポジトリをインストールします。

$ git clone git@github.com:lotsofbytes/larajapan.gitcat

次に、phanのパッケージをインストールします(注1)

$ cd larajapan
$ composer require --dev phan/phan

インストールできたところで、phanの設定をします。

.phanのディレクトリを作成して、config.phpのファイルを作成します。


return [
    // 解析のためにクラスやメソッドの情報を取得するディレクトリ。
    // これらと、以下の exclude_analysis_directory_list で指定した
    // ディレクトリとの差分が解析チェックの対象となる
    'directory_list' => [
        'app',
        'routes',
        'vendor'
    ],

    // 解析チェックの対象から除外するディレクトリ。
    'exclude_analysis_directory_list' => [
       'vendor'
    ],
];

設定ファイルが用意できたところで、最初のphanの実行をしてみましょう。

$ vendor/bin/phan -p 
app/Auth/Notifications/ResetPassword.php:51 PhanTypeMismatchArgument Argument 2 (parameters) is string but \route() takes array defined at vendor/laravel/framework/src/Illuminate/Foundation/helpers.php:707
app/Auth/Passwords/CanResetPassword.php:16 PhanUndeclaredProperty Reference to undeclared property \App\Auth\Passwords\CanResetPassword->email
app/Auth/Passwords/CanResetPassword.php:27 PhanUndeclaredMethod Call to undeclared method \App\Auth\Passwords\CanResetPassword::notify
app/Providers/RouteServiceProvider.php:54 PhanUndeclaredMethod Call to undeclared method \Illuminate\Routing\Route::namespace
app/Providers/RouteServiceProvider.php:68 PhanUndeclaredMethod Call to undeclared method \Illuminate\Routing\Route::namespace
routes/api.php:16 PhanUndeclaredClassMethod Call to method middleware from undeclared class \Route
routes/channels.php:14 PhanUndeclaredClassMethod Call to method channel from undeclared class \Broadcast
routes/console.php:16 PhanUndeclaredClassMethod Call to method command from undeclared class \Artisan
routes/console.php:17 PhanUndeclaredVariable Variable $this is undeclared
routes/web.php:14 PhanUndeclaredClassMethod Call to method get from undeclared class \Route
routes/web.php:22 PhanUndeclaredClassMethod Call to method get from undeclared class \Route
routes/web.php:23 PhanUndeclaredClassMethod Call to method post from undeclared class \Route
routes/web.php:24 PhanUndeclaredClassMethod Call to method post from undeclared class \Route
routes/web.php:27 PhanUndeclaredClassMethod Call to method get from undeclared class \Route
routes/web.php:28 PhanUndeclaredClassMethod Call to method post from undeclared class \Route
routes/web.php:31 PhanUndeclaredClassMethod Call to method get from undeclared class \Route
routes/web.php:32 PhanUndeclaredClassMethod Call to method post from undeclared class \Route
routes/web.php:33 PhanUndeclaredClassMethod Call to method get from undeclared class \Route
routes/web.php:34 PhanUndeclaredClassMethod Call to method post from undeclared class \Route
routes/web.php:36 PhanUndeclaredClassMethod Call to method get from undeclared class \Route

たくさんエラー出てきました。

その中でもPhanUndeclaredClassMethodが多い。これは宣言されていないクラスのメソッドをコールしているエラーです。

例えば、一番多いエラーとなっているRouteのクラスは、Laravel特有のFacade(ファサード)です。ネームスペースnamespaceがあるクラスと違い、phanにとっては解析が難しいようです。

調査したところ、IDEヘルパーの開発ツールを使用して、stabのファイルを作成することで回避できそうです。Laravelのstabのファイルの作成には、まず以下を実行してide-helperのパッケージをインストールします。

$ composer require --dev barryvdh/laravel-ide-helper

次に、config/app.phpを編集して以下を追加、

Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class

このパッケージは、debugbarの開発者が作成したものです。本来は、phpStormなどのIDEのエディターでのヘルパーが目的(それゆえにlaravel-ide-helper)ですが、ファサードのクラスのメソッドを宣言してくれるために、phanでも役に立つようです。

..
    'providers' => [  
..
        /*                                                                                                                                                                                                         
        * Package Service Providers...                                                                                                                                                                            
        */                                                                                                                                                                                                        
        Laravel\Tinker\TinkerServiceProvider::class,                                                                                                                                                               
        Barryvdh\Debugbar\ServiceProvider::class,
        Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,

..

そして、以下を実行。

$ php artisan ide-helper:generate

_ide_helper.phpのファイルが実行したディレクトリに作成されます。これを、.phan/stabsのディレクトリを作成して移します。

$ mkdir .phan/stabs
$ mv _ide_helper.php .phan/stabs

さらに、このファイルを利用するために、.phan/config.phpも編集が必要です。


return [
    // 解析のためにクラスやメソッドの情報を取得するディレクトリ。
    // これらと、以下の exclude_analysis_directory_list で指定した
    // ディレクトリとの差分が解析チェックの対象となる
    'directory_list' => [
     '.phan/stabs',
        'app',
        'routes',
        'vendor'
    ],

    // 解析チェックの対象から除外するディレクトリ。
    'exclude_analysis_directory_list' => [
       '.phan',
       'vendor'
    ],
];

これで実行準備完了です。再度実行してみましょう。

$ vendor/bin/phan -p
app/Auth/Notifications/ResetPassword.php:51 PhanTypeMismatchArgument Argument 2 (parameters) is string but \route() takes array defined at vendor/laravel/framework/src/Illuminate/Foundation/helpers.php:707
app/Auth/Passwords/CanResetPassword.php:16 PhanUndeclaredProperty Reference to undeclared property \App\Auth\Passwords\CanResetPassword->email
app/Auth/Passwords/CanResetPassword.php:27 PhanUndeclaredMethod Call to undeclared method \App\Auth\Passwords\CanResetPassword::notify
app/Providers/RouteServiceProvider.php:54 PhanUndeclaredStaticMethod Static call to undeclared method \Illuminate\Support\Facades\Route::middleware
app/Providers/RouteServiceProvider.php:68 PhanUndeclaredStaticMethod Static call to undeclared method \Illuminate\Support\Facades\Route::prefix
routes/api.php:16 PhanUndeclaredStaticMethod Static call to undeclared method \Route::middleware
routes/channels.php:14 PhanUndeclaredStaticMethod Static call to undeclared method \Broadcast::channel
routes/console.php:17 PhanUndeclaredVariable Variable $this is undeclared

PhanUndeclaredClassMethodのエラーが少なくなりました。しかし、残り未宣言のエラーの解決は現段階では無理そうです。実際宣言されていにも関わらず、その情報をphanに伝えることが自動のツールでは不可能だからです。将来に期待しましょう。

とりあえず、ここでは-iオプションで、この未宣言のエラーを無視することにします。

$ vendor/bin/phan -p -i
app/Auth/Notifications/ResetPassword.php:51 PhanTypeMismatchArgument Argument 2 (parameters) is string but \route() takes array defined at vendor/laravel

-iで、エラーは1つだけとなりました。

このエラーは、以下のroute()の2番目の引数が配列である必要があるということで、

..
   public function toMail($notifiable)                                                                                                                                                                            
    {                                                                                                                                                                                                              
        return (new MailMessage)                                                                                                                                                                                   
            ->subject('パスワードリセット')                                                                                                                                                                        
            ->greeting('パスワードリセット')                                                                                                                                                                       
            ->line('パスワードリセットリンクの送信のリクエストがありました。')                                                                                                                                     
            ->action('リセットパスワード', url(config('app.url').route('password.reset', [$this->token], false)))                                                                                                    
            ->line('リクエストされていなかったら、無視してください。');                                                                                                                                            
    }     
..

と編集して解決できました。

ということで、インストールは”そう楽しくなさそうな”ツールですが、もし大きなプログラムがあるなら一度でも試してください。私のプロジェクトではいくつか重要なバグをみつけてくれてそれだけでも十分な貢献です。これからも、検証のツールとしてunit testとともに重要なものになりそうです。

ひとつ注意することは、この実行には空きメモリが2GB以上必要です。残念ながら私のテスト環境のawsのsmall(メモリ2GB)のインスタンスでは実行不可能でした。仮想の開発環境(メモリ4GB)で実行可能でした。

注1

composerで、phpdocumentor/reflection-docblockのバージョンのコンフリクトが出ました。すでにインストールされているそのパッケージのバージョンは4.1.1ですが、phan/phanが必要とするものはそれよりも昔のバージョン。ということで、

..
   "require": {
        "php": ">=5.6.4",
        "barryvdh/laravel-debugbar": "~2.4",
        "laravel/framework": "5.4.*",
        "laravel/tinker": "~1.0",
        "phpdocumentor/reflection-docblock": "4.1.1 as 3.3.0"
    },
..

4.1.1 as 3.3.0として4.1.1のバージョンを3.3.0のバージョンとして扱ってもらうことで解決できました。

Laravelのアップグレードとgitでのバージョン管理

Gitは、開発になくてはならないバージョン管理のツール。Laravelのバージョンをアップグレードがあるときは、フォルダーやファイルが入れ替わるためにGitで新ブランチを作成してから、更新作業を行います。しかし、問題はこの新ブランチは現在のマスターの複製であるため、以前のバージョンのファイルがすでに存在することです。

新しいバージョンと入れ替える1つの方法は、.git以外のフォルダーやファイルをすべて削除して、そこに新バージョンのLaravelを入れ直す。しかし、これを行うとGitで保存されている以前の変更やログぼ履歴が残ってしまい、新しくゼロからスタートができません。新しいバージョンでの開発は、あたかもまっさらからのように始めたい。

前回のLaravel 5.4のレポジトリ作成時にこの問題とぶつかり、どうしたら良いものやらと調査した結果、以下のような手順になりました。

masterを5.3ブランチと改名

現在は、masterにいます。

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

 
masterを5.3のブランチに改名します。

$ git branch -m master 5.3

 
リモートレポジトリ(github)にプッシュします。

$ git push origin 5.3

 

5.4ブランチを作成してmasterに改名

現在は、5.3のブランチにいます。

$ git branch -a
* 5.3
  remotes/origin/5.3
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

 
まずは、親なしの5.4ブランチを作成します。親なし--orphanとすることによりブランチの変更やログの履歴が削除されます。この時点ではすべてのフォルダーやファイルはチェックアウトされていません。そして、それらをresetで全部削除します。残っているのは、.gitのディレクトリのみです。

$ git checkout --orphan 5.4
$ git reset --hard

 
次に、ここにLaravel 5.4の初期ファイルを入れますが、.gitのディレクトリがあるためにエラーがでて入れることができません。それゆえに、一旦tmpのディレクトリに入れてから中身を移します。.env.exampleなどのドットファイルも移していることに注意してください。.gitを違うディレクトリに移してから、同ディレクトリに作成してもいいですね。そして、gitのディレクトリを戻す。

$ composer create-project --prefer-dist laravel/laravel tmp 5.4.*
$ mv tmp/* tmp/.[^.]* .
$ rmdir tmp

 
追加されたフォルダーやファイルをコミットします。そして、5.4ブランチをmasterに改名します。

$ git add .
$ git commit -m 'Laravel 5.4 init'
$ git branch -m master

 
そして最後にレポジトリ(github)にプッシュします。全部入れ替えるので --forceオプションが必要です。

$ git push -u origin master --force
$ git branch -a
* master
  remotes/origin/5.3
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

ということで、githubでは以下のようにドロップダウンに2つのブランチが見えます。

Laravelの日本語レポジトリの作成(Laravel 5.4)

前回に作成したLaravelの日本語のリポジトリ(Laravel 5.4)。今回はその作成の仕方を説明します。ほとんどは、Laravel 5.3のときと同じですが、いくつか違いがあります。

コマンドの実行

まずは、以下のcomposerのコマンドを実行します。

composer create-project --prefer-dist laravel/laravel larajapan 5.4.*	

上で使用されているコマンドの引数は、

--prefer-dist laravel/laravel

https://packagist.org/packages/laravel/laravelからパッケージをダウンロードすることを指示します。

larajapan

パッケージのダウンロード先。その名前でディレクトリを作成します。このディレクトリ名は、先のコマンドラインで違う名前を指定可能であるし、実行完了してから改名も可能です。

5.4.*

パッケージのバージョンを指定。ここでは、laravelの5.4を使用します。マイナーバージョンを指定したいなら、5.4.36のように指定します。

実行すると、パッケージに含まれるファイル、さらにパッケージが依存するパッケージのファイルが多数ダウンロードされ少々時間がかかります。

最終的には、実行したディレクトリのもとにlarajapanのディレクトリが作成され、ダウンロードされたファイルが収納されます。

larajapan
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── resources/
├── routes/
├── storage/
├── tests/
├── vendor/
├── artisan
├── composer.json
├── composer.lock
├── package.json
├── phpunit.xml
├── readme.md
├── server.php
└── webpack.mix.js

*5.3と違いgulpfile.jsの代わりにwebpack.mix.jsがあります。asset(sassやjs)のコンパイルは、gulpでなくwebpackを使用したlaravel-mixに変わったからです。

次に、ユーザー認証のためのファイル作成を以下の実行で行います。

php artisan make:auth	

この実行により、resources/viewsのディレクトリにおいて、すでにインストールされている以下のコントローラで使用されるbladeファイルが作成されます。

app/Http/Controllers
├── Auth/
│   ├── ForgotPasswordController.php(パスワードのリセットのリンク送信画面)
│   ├── LoginController.php(ログイン画面)
│   ├── RegisterController.php(会員登録画面)
│   └── ResetPasswordController.php(パスワードリセット画面)
├── Controller.php
└── HomeController.php(ログイン後のホーム画面)

最後に以下のコマンドを実行して、先のパスワードのリセットのリンク送信画面から発行されるEメールので使用されるHTMLのテンプレートを作成作成します。

php artisan vendor:publish

日本語化

さて、ここからが日本語化の作業です。

まず、config/app.phpの編集から。

                                                                                                                                                                                                                                                                                                                                                   
...
    'timezone' => 'UTC',                                                                                                                                                                                                                                                                                                               
                                                                                                                                                                                              
    'locale' => 'en',
...

                                                                                                                                                                                                                                                                                                                                                   
...
    'timezone' => 'Asia/Tokyo',                                                                                                                                                                                                                                                                                                               
                                                                                                                                                                                              
    'locale' => 'ja',
...

と変えて保存します。

timezone

これは、通常、プログラム内の日時設定のタイムゾーンとして使用されるもので、PHPの以下の関数で使用されます。

date_default_timezone_set()

ここで設定すれば、後はLaravelが面倒みてくれます。

日本時間の場合は、Aisa/Tokyoの設定だけで十分。

locale

resources/langで言語のファイルが以下のように存在します。これらは、Laravelのプロジェクトでバリデーションのエラーメッセージなどを定義しています。

resources/lang
└── en/
    ├── auth.php
    ├── pagination.php
    ├── passwords.php
    └── validation.php

デフォルトの設定では、英語のenのディレクトリしかありません。日本語の翻訳を作成するには、上で設定したjaと同じ名前のディレクトリをそこに作成します。以下の実行でディレクトリごとコピーしてください。

$ cp -pr en ja

バリデーションに関しては、見米氏のバリデーション(1)Validatorファサードのextend を参照してください。

今回は、新しく追加されたバリデーションの以下のエントリーもあります。

after_or_equal
before_or_equal

次は、ユーザー認証画面などで使用されるブレードファイルの翻訳です。

私のLaravelの日本語レポジトリでは、以下は、すべて翻訳してあります。

resources/views
├── auth
│   ├── login.blade.php
│   ├── passwords
│   │   ├── email.blade.php
│   │   └── reset.blade.php
│   └── register.blade.php
├── errors
│   └── 404.blade.php
├── home.blade.php
├── layouts
│   └── app.blade.php
├── vendor
│   ├── mail
│   │   ├── html
│   │   │   ├── button.blade.php
│   │   │   ├── footer.blade.php
│   │   │   ├── header.blade.php
│   │   │   ├── layout.blade.php
│   │   │   ├── message.blade.php
│   │   │   ├── panel.blade.php
│   │   │   ├── promotion
│   │   │   │   └── button.blade.php
│   │   │   ├── promotion.blade.php
│   │   │   ├── subcopy.blade.php
│   │   │   ├── table.blade.php
│   │   │   └── themes
│   │   │       └── default.css
│   │   └── markdown
│   │       ├── button.blade.php
│   │       ├── footer.blade.php
│   │       ├── header.blade.php
│   │       ├── layout.blade.php
│   │       ├── message.blade.php
│   │       ├── panel.blade.php
│   │       ├── promotion
│   │       │   └── button.blade.php
│   │       ├── promotion.blade.php
│   │       ├── subcopy.blade.php
│   │       └── table.blade.php
│   ├── notifications
│   │   └── email.blade.php
│   └── pagination
│       ├── bootstrap-4.blade.php
│       ├── default.blade.php
│       ├── simple-bootstrap-4.blade.php
│       └── simple-default.blade.php
└── welcome.blade.php

5.3と違い、errorsのフォルダーは作成されていませんでしたので追加しました。また、vendor/mailのフォルダーは新規のもので、HTMLだけでなくマークダウンも対応しています。

パスワードリセットで送信されるEメールの翻訳

ここまで来ても、残念ながら、パスワードを忘れたときに送信される、パスワードリセットを含むEメールの内容がまだ翻訳されていません。なぜなら、本文がハードコードされているからです。

これはちょっと頭をひねりましたが、多分以下が最小の変更で対応できると思います。

まず、

vendor/laravel/framework/src/Illuminate/AuthのディレクトリからResetPassword.phpCanResetPassword.phpのファイルを以下の場所にコピーします。

app/Auth
├── Notifications/
│   └── ResetPassword.php
└── Passwords/
    └── CanResetPassword.php

次に、以下のようにファイルを編集します。app/User.phpのファイルも変更必要です。

namespace App\Auth\Notifications;                                                                                                                                                             
                                                                                                                                                                                              
use Illuminate\Notifications\Notification;                                                                                                                                                    
use Illuminate\Notifications\Messages\MailMessage;                                                                                                                                            
                                                                                                                                                                                              
class ResetPassword extends Notification                                                                                                                                                      
{                  
...
    /**                                                                                                                                                                                       
     * Build the mail representation of the notification.                                                                                                                                     
     *                                                                                                                                                                                        
     * @param  mixed  $notifiable                                                                                                                                                             
     * @return \Illuminate\Notifications\Messages\MailMessage                                                                                                                                 
     */                                                                                                                                                                                       
    public function toMail($notifiable)                                                                                                                                                       
    {    
        return (new MailMessage)
            ->subject('パスワードリセット')
          ->greeting('パスワードリセット')
            ->line('パスワードリセットリンクの送信のリクエストがありました。')
            ->action('リセットパスワード', url(config('app.url').route('password.reset', $this->token, false)))
            ->line('リクエストされていなかったら、無視してください。');                                                                                                          
    }     
}
namespace App\Auth\Passwords;                                                                                                                                                                 
                                                                                                                                                                                              
use App\Auth\Notifications\ResetPassword as ResetPasswordNotification;                                                                                                                        
                                                                                                                                                                                              
trait CanResetPassword                                                                                                                                                                        
{                                                                                                                                                                                             
    /**                                                                                                                                                                                       
     * Get the e-mail address where password reset links are sent.                                                                                                                            
     *                                                                                                                                                                                        
     * @return string                                                                                                                                                                         
     */                                                                                                                                                                                       
    public function getEmailForPasswordReset()                                                                                                                                                
    {                                                                                                                                                                                         
        return $this->email;                                                                                                                                                                  
    }                                                                                                                                                                                         
                                                                                                                                                                                              
    /**                                                                                                                                                                                       
     * Send the password reset notification.                                                                                                                                                  
     *                                                                                                                                                                                        
     * @param  string  $token                                                                                                                                                                 
     * @return void                                                                                                                                                                           
     */                                                                                                                                                                                       
    public function sendPasswordResetNotification($token)                                                                                                                                     
    {                                                                                                                                                                                         
        $this->notify(new ResetPasswordNotification($token));                                                                                                                                 
    }                                                                                                                                                                                         
}                                                                                                                                                                                             
         
                                                                                                                                                                                              
namespace App;                                                                                                                                                                                
                                                                                                                                                                                              
use Illuminate\Notifications\Notifiable;                                                                                                                                                      
use Illuminate\Foundation\Auth\User as Authenticatable;                                                                                                                                       
use App\Auth\Passwords\CanResetPassword;                                                                                                                                                      
                                                                                                                                                                                              
class User extends Authenticatable                                                                                                                                                            
{                                                                                                                                                                                             
    use Notifiable;                                                                                                                                                                           
    use CanResetPassword;                                                                                                                                                                     
...                         

認証のroutesの設定

日本語化とは関係ないですが、私がこうした方がわかりやすいと思ったことです。

オリジナルのroutes.phpは、

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index');

とシンプルですが、Auth::routes()で認証のrouteが隠されてしまって不透明。

ということで、私のLaravelの日本語レポジトリでは、以下のように編集しました。

// 以下は、Auth::routes()の中身を移したもの。将来において変更が可能なように                                                                                                                   
                                                                                                                                                                                              
// Authentication Routes...
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login', 'Auth\LoginController@login');
Route::post('logout', 'Auth\LoginController@logout')->name('logout');

// Registration Routes...
Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
Route::post('register', 'Auth\RegisterController@register');

// Password Reset Routes...
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
Route::post('password/reset', 'Auth\ResetPasswordController@reset');                                                                                                                
                                                                                                                                                                                              
Route::get('/home', 'HomeController@index');  

その他

インストールにおいて経験した問題として、

php artisan migrate	

を実行したときに、以下のエラーとなりました。

[Illuminate\Database\QueryException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQL: alter table users add unique users_email_unique(email))

[PDOException]
SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes

これは、使用しているデータベースがMariaDBで10.2.2のバージョンより古いか、あるいはMySQLで5.7.7より古いときに起こるエラーです。Laravelは5.4より、絵文字対応のutf8mb4のエンコーディングがデフォルトとなりました。

古いバージョンを使用する場合(大半がそうでは?)は、以下の変更が必要となります。日本語のレポにはこの変更が含まれています。


namespace App\Providers;                                                                                                                                                                                           
                                                                                                                                                                                                                   
use Illuminate\Support\ServiceProvider;                                                                                                                                                                            
use Illuminate\Support\Facades\Schema;                                                                                                                                                                             
                                                                                                                                                                                                                   
class AppServiceProvider extends ServiceProvider                                                                                                                                                                   
{                                                                                                                                                                                                                  
    /**                                                                                                                                                                                                            
     * Bootstrap any application services.                                                                                                                                                                         
     *                                                                                                                                                                                                             
     * @return void                                                                                                                                                                                                
     */                                                                                                                                                                                                            
    public function boot()                                                                                                                                                                                         
    {                                                                                                                                                                                                              
        Schema::defaultStringLength(191);                                                                                                                                                                          
    }                                                                                                                                                                                                              
                                                                                                                                                                                                                   
    /**                                                                                                                                                                                                            
     * Register any application services.                                                                                                                                                                          
     *                                                                                                                                                                                                             
     * @return void                                                                                                                                                                                                
     */                                                                                                                                                                                                            
    public function register()                                                                                                                                                                                     
    {                                                                                                                                                                                                              
        //                                                                                                                                                                                                         
    }                                                                                                                                                                                                              
}   

Laravelの日本語のレポジトリ(Laravel 5.4)

Laravelの日本語のリポジトリをLaravel5.4に更新しました。もうLaravel5.5がリリースされていますが、Laravel5.5と違ってLaravel5.4は、php7でなくphp5.6のバージョンで動作する最後のバージョンなゆえに重要です。前回同様に、以下インストールの手順です。

Laravelにおいて新規のプロジェクト作成はとても簡単。コマンドラインでいくつかのコマンドを実行をちょちょいとすれば完了。しかし、インストールされるのは英語のプロジェクト。テンプレートやメッセージの翻訳をいちいちしなければ日本語のプロジェクトにはならない。

ここのプロセスを簡単にと、Laravelバージョン5.4をもとに、開発者のために日本語化したレポジトリを作成してみました。

このレポジトリには、

  • デフォルトのユーザー認証の機能:会員登録、パスワードリセット、会員ログイン
  • 日本語に翻訳されたデフォルトのテンプレートとEメールメッセージ
  • 日本語に翻訳されたデフォルトの入力エラーメッセージ
  • デバッグのためのDebugbarツール
  • ウェブ解析ツールGoogle Analyticsのトラッキングスクリプト

以上を含みます。

さらに、今回は、実際動作するデモとして以下に用意しました。
https://larajapan.lotsofbytes.com/larajapan

さて、このレポジトリのインストールは以下の手順で簡単にできます。

レポジトリのインストール

SSHを利用しているなら、以下をコマンドラインで実行してレポジトリをインストールします。

$ git clone git@github.com:lotsofbytes/larajapan.git

あるいは、Httpsを使用するなら、以下を実行します。

$ git clone https://github.com/lotsofbytes/larajapan.git

実行後は、larajapanのディレクトリが作成されますが、お好きな名前に改名してもらってOKです。

その後、

cd larajapan

それから、ファイルのパーミッションを与えるべく以下を実行。

$ chmod -R a+w bootstrap/cache
$ chmod -R a+w storage

インストール後は、以下を実行してください。

$ composer install

.envの編集

.env.example をコピーして、.env を作成し編集して以下のように設定します。*****の部分を適切な値に変更してください。

APP_NAME=私のララベル
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_DATABASE=*****
DB_USERNAME=*****
DB_PASSWORD=*****
MAIL_DRIVER=sendmail

その後、以下を実行して.env内のAPP_KEYを更新します。

$ php artisan key:generate

APP_DEBUG=trueこれによりDebugbarが画面下方に表示されます。Debugbarに関しては、Debugbarで楽々デバッグも読んでください。

また、
ANALYTICS=UA-XXXXXXのようにサイトのためにGoogleから取得したコードを設定すれば、Google Analyticsでウェブでのユーザーの動向が追跡できます。

DBを作成

.envで指定したDBを作成。

$ echo 'CREATE DATABASE larajapan CHARACTER SET utf8' | mysql -u root -p
php artisan migrate

ウェブサーバーの立ち上げ

最後に以下を実行して、ウェブサーバーを立ち上げると、

$ php artisan serve

以下のアドレスでブラウザーからアクセスできます。

http://localhost:8000

以上です。

上の設定は私の開発環境Fedora LinuxとAmazon Linux OSで、7.0のバージョンのPHPで、DBにはMysqlあるいはMaria DBを使用して動作確認しています。しかし、皆さんの環境ではいろいろ異なることがあると思います。問題や指摘があれば、ご連絡ください。

Laravelの日本語レポジトリの作成(Laravel 5.3)

前回に作成したLaravelの日本語レポジトリ(Laravel 5.3)。今回はその作成の仕方を説明します。

コマンドの実行

まずは、以下のcomposerのコマンドを実行します。

composer create-project --prefer-dist laravel/laravel larajapan 5.3.*	

上で使用されているコマンドの引数は、

--prefer-dist laravel/laravel

https://packagist.org/packages/laravel/laravelからパッケージをダウンロードすることを指示します。

larajapan

パッケージのダウンロード先。その名前でディレクトリを作成します。このディレクトリ名は、先のコマンドラインで違う名前を指定可能であるし、実行完了してから改名も可能です。

5.3.*

パッケージのバージョンを指定。ここでは、laravelの5.3を使用します。マイナーバージョンを指定したいなら、5.3.30のように指定します。

実行すると、パッケージに含まれるファイル、さらにパッケージが依存するパッケージのファイルが多数ダウンロードされ少々時間がかかります。

最終的には、実行したディレクトリのもとにlarajapanのディレクトリが作成され、ダウンロードされたファイルが収納されます。

larajapan
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── resources/
├── routes/
├── storage/
├── tests/
├── vendor/
├── artisan*
├── composer.json
├── composer.lock
├── gulpfile.js
├── package.json
├── phpunit.xml
├── readme.md
└── server.php

次に、ユーザー認証のためのファイル作成を以下の実行で行います。

php artisan make:auth	

この実行により、resources/viewsのディレクトリにおいて、すでにインストールされている以下のコントローラで使用されるbladeファイルが作成されます。

app/Http/Controllers
├── Auth/
│   ├── ForgotPasswordController.php(パスワードのリセットのリンク送信画面)
│   ├── LoginController.php(ログイン画面)
│   ├── RegisterController.php(会員登録画面)
│   └── ResetPasswordController.php(パスワードリセット画面)
├── Controller.php
└── HomeController.php(ログイン後のホーム画面)

最後に以下のコマンドを実行して、先のパスワードのリセットのリンク送信画面から発行されるEメールので使用されるHTMLのテンプレートを作成作成します。

php artisan vendor:publish

日本語化

さて、ここからが日本語化の作業です。

まず、config/app.phpの編集から。

                                                                                                                                                                                                                                                                                                                                                   
...
    'timezone' => 'UTC',                                                                                                                                                                                                                                                                                                               
                                                                                                                                                                                              
    'locale' => 'en',
...

                                                                                                                                                                                                                                                                                                                                                   
...
    'timezone' => 'Asia/Tokyo',                                                                                                                                                                                                                                                                                                               
                                                                                                                                                                                              
    'locale' => 'ja',
...

と変えて保存します。

timezone

これは、通常、プログラム内の日時設定のタイムゾーンとして使用されるもので、PHPの以下の関数で使用されます。

date_default_timezone_set()

ここで設定すれば、後はLaravelが面倒みてくれます。

日本時間の場合は、Aisa/Tokyoの設定だけで十分。

locale

resources/langで言語のファイルが以下のように存在します。これらは、Laravelのプロジェクトでバリデーションのエラーメッセージなどを定義しています。

resources/lang
└── en/
    ├── auth.php
    ├── pagination.php
    ├── passwords.php
    └── validation.php

デフォルトの設定では、英語のenのディレクトリしかありません。日本語の翻訳を作成するには、上で設定したjaと同じ名前のディレクトリをそこに作成します。以下の実行でディレクトリごとコピーしてください。

$ cp -pr en ja

バリデーションに関しては、見米氏のバリデーション(1)Validatorファサードのextend を参照してください。

次は、ユーザー認証画面などで使用されるブレードファイルの翻訳です。

私のLaravelの日本語レポジトリでは、以下は、すべて翻訳してあります。

resources/views
├── auth
│   ├── login.blade.php
│   ├── passwords
│   │   ├── email.blade.php
│   │   └── reset.blade.php
│   └── register.blade.php
├── errors
│   └── 503.blade.php
├── home.blade.php
├── layouts
│   └── app.blade.php
├── vendor
│   ├── notifications
│   │   ├── email.blade.php
│   │   └── email-plain.blade.php
│   └── pagination
│       ├── bootstrap-4.blade.php
│       ├── default.blade.php
│       ├── simple-bootstrap-4.blade.php
│       └── simple-default.blade.php
└── welcome.blade.php

翻訳とは関係ありませんが、app.blade.phpのレイアウトで参照されている、app.cssapp.jsには、url()を入れて、プロジェクトがインストールされるディレクトリが変わっても参照されるようにしてあります。

パスワードリセットで送信されるEメールの翻訳

ここまで来ても、残念ながら、パスワードを忘れたときに送信される、パスワードリセットを含むEメールの内容がまだ翻訳されていません。なぜなら、本文がハードコードされているからです。

これはちょっと頭をひねりましたが、多分以下が最小の変更で対応できると思います。

まず、

vendor/laravel/framework/src/Illuminate/AuthのディレクトリからResetPassword.phpCanResetPassword.phpのファイルを以下の場所にコピーします。

app/Auth
├── Notifications/
│   └── ResetPassword.php
└── Passwords/
    └── CanResetPassword.php

次に、以下のようにファイルを編集します。app/User.phpのファイルも変更必要です。

namespace App\Auth\Notifications;                                                                                                                                                             
                                                                                                                                                                                              
use Illuminate\Notifications\Notification;                                                                                                                                                    
use Illuminate\Notifications\Messages\MailMessage;                                                                                                                                            
                                                                                                                                                                                              
class ResetPassword extends Notification                                                                                                                                                      
{                  
...
    /**                                                                                                                                                                                       
     * Build the mail representation of the notification.                                                                                                                                     
     *                                                                                                                                                                                        
     * @param  mixed  $notifiable                                                                                                                                                             
     * @return \Illuminate\Notifications\Messages\MailMessage                                                                                                                                 
     */                                                                                                                                                                                       
    public function toMail($notifiable)                                                                                                                                                       
    {                                                                                                                                                                                         
        return (new MailMessage)                                                                                                                                                              
            ->subject('パスワードリセット')                                                                                                                                                   
            ->greeting('パスワードリセット')                                                                                                                                                  
            ->line('パスワードリセットリンクの送信のリクエストがありました。')                                                                                                                
            ->action('リセットパスワード', url('password/reset', $this->token))                                                                                                               
            ->line('リクエストされていなかったら、無視してください。');                                                                                                                       
    }     
}
namespace App\Auth\Passwords;                                                                                                                                                                 
                                                                                                                                                                                              
use App\Auth\Notifications\ResetPassword as ResetPasswordNotification;                                                                                                                        
                                                                                                                                                                                              
trait CanResetPassword                                                                                                                                                                        
{                                                                                                                                                                                             
    /**                                                                                                                                                                                       
     * Get the e-mail address where password reset links are sent.                                                                                                                            
     *                                                                                                                                                                                        
     * @return string                                                                                                                                                                         
     */                                                                                                                                                                                       
    public function getEmailForPasswordReset()                                                                                                                                                
    {                                                                                                                                                                                         
        return $this->email;                                                                                                                                                                  
    }                                                                                                                                                                                         
                                                                                                                                                                                              
    /**                                                                                                                                                                                       
     * Send the password reset notification.                                                                                                                                                  
     *                                                                                                                                                                                        
     * @param  string  $token                                                                                                                                                                 
     * @return void                                                                                                                                                                           
     */                                                                                                                                                                                       
    public function sendPasswordResetNotification($token)                                                                                                                                     
    {                                                                                                                                                                                         
        $this->notify(new ResetPasswordNotification($token));                                                                                                                                 
    }                                                                                                                                                                                         
}                                                                                                                                                                                             
         
                                                                                                                                                                                              
namespace App;                                                                                                                                                                                
                                                                                                                                                                                              
use Illuminate\Notifications\Notifiable;                                                                                                                                                      
use Illuminate\Foundation\Auth\User as Authenticatable;                                                                                                                                       
use App\Auth\Passwords\CanResetPassword;                                                                                                                                                      
                                                                                                                                                                                              
class User extends Authenticatable                                                                                                                                                            
{                                                                                                                                                                                             
    use Notifiable;                                                                                                                                                                           
    use CanResetPassword;                                                                                                                                                                     
...                         

認証のroutesの設定

日本語化とは関係ないですが、私がこうした方がわかりやすいと思ったことです。

オリジナルのroutes.phpは、

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index');

とシンプルですが、Auth::routes()で認証のrouteが隠されてしまって不透明。

ということで、私のLaravelの日本語レポジトリでは、以下のように編集しました。

// 以下は、Auth::routes()の中身を移したもの。将来において変更が可能なように                                                                                                                   
                                                                                                                                                                                              
// Authentication Routes...                                                                                                                                                                   
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');                                                                                                                     
Route::post('login', 'Auth\LoginController@login');                                                                                                                                           
Route::post('logout', 'Auth\LoginController@logout')->name('logout');                                                                                                                         
                                                                                                                                                                                              
// Registration Routes...                                                                                                                                                                     
Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');                                                                                                     
Route::post('register', 'Auth\RegisterController@register');                                                                                                                                  
                                                                                                                                                                                              
// Password Reset Routes...                                                                                                                                                                   
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm');                                                                                                            
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');                                                                                                            
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');                                                                                                           
Route::post('password/reset', 'Auth\ResetPasswordController@reset');                                                                                                                          
                                                                                                                                                                                              
Route::get('/home', 'HomeController@index');  

最後に

Laravelの最新バージョンは今週に5.3から5.4となりました。今回説明した(前回に作成したLaravelの日本語レポジトリも)はバージョン5.3をベースにしたものです。しかし、バージョン5.4での日本語レポジトリ作成もいくつかファイルは違いますが、基本的には、最初のcomposerの実行で、5.3.*の代わりに、5.4.*と指定すれば、同じような手順で作成できます。

Laravelの日本語レポジトリ(Laravel 5.3)

** Laravelの日本語レポジトリをLaravel 5.4に更新したために手順において更新があります **

Laravelにおいて新規のプロジェクト作成はとても簡単。コマンドラインでいくつかのコマンドを実行をちょちょいとすれば完了。しかし、インストールされるのは英語のプロジェクト。テンプレートやメッセージの翻訳をいちいちしなければ日本語のプロジェクトにはならない。

ここのプロセスを簡単にと、Laravelバージョン5.3をもとに、開発者のために日本語化したレポジトリを作成してみました。

このレポジトリには、

  • デフォルトのユーザー認証の機能:会員登録、パスワードリセット、会員ログイン
  • 日本語に翻訳されたデフォルトのテンプレートとEメールメッセージ
  • 日本語に翻訳されたデフォルトの入力エラーメッセージ
  • デバッグのためのDebugbarツール
  • ウェブ解析ツールGoogle Analyticsのトラッキングスクリプト

以上を含みます。

さらに、今回は、実際動作するデモとして以下に用意しました。
https://larajapan.lotsofbytes.com/larajapan

さて、このレポジトリのインストールは以下の手順で簡単にできます。

レポジトリのインストール

SSHを利用しているなら、以下をコマンドラインで実行してレポジトリをインストールします。

$ git clone -b 5.3 git@github.com:lotsofbytes/larajapan.git

あるいは、Httpsを使用するなら、以下を実行します。

$ git clone -b 5.3 https://github.com/lotsofbytes/larajapan.git

実行後は、larajapanのディレクトリが作成されますが、お好きな名前に改名してもらってOKです。

その後、

cd larajapan

それから、ファイルのパーミッションを与えるべく以下を実行。

$ chmod -R a+w storage

インストール後は、以下を実行してください。

$ composer install

以下も必要かもしれません。

$ composer update

.envの編集

.env.example をコピーして、.env を作成し編集して以下のように設定します。*****の部分を適切な値に変更してください。

APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_DATABASE=*****
DB_USERNAME=*****
DB_PASSWORD=*****
MAIL_DRIVER=sendmail

その後、以下を実行して.env内のAPP_KEYを更新します。

$ php artisan key:generate

APP_DEBUG=trueこれによりDebugbarが画面下方に表示されます。Debugbarに関しては、Debugbarで楽々デバッグも読んでください。

また、
ANALYTICS=UA-XXXXXXのようにサイトのためにGoogleから取得したコードを設定すれば、Google Analyticsでウェブでのユーザーの動向が追跡できます。

DBを作成

.envで指定したDBを作成。

$ echo 'CREATE DATABASE larajapan CHARACTER SET utf8' | mysql -u root -p
php artisan migrate

ウェブサーバーの立ち上げ

最後に以下を実行して、ウェブサーバーを立ち上げると、

$ php artisan serve

以下のアドレスでブラウザーからアクセスできます。

http://localhost:8000

以上です。

上の設定は私の開発環境Fedora LinuxとAmazon Linux OSで、5.6のバージョンのPHPで、DBにはMysqlあるいはMaria DBを使用して動作確認しています。しかし、皆さんの環境ではいろいろ異なることがあると思います。問題や指摘があれば、ご連絡ください。

パブリックキーを使用してsftp (2) sftponlyの環境で

前回では、Laravel Collective Remoteを利用して、sftpでファイルをアップロード・ダウンロードする話をしました。それだけで事はほとんど足りるのですが、1つ困ったことがありました。

sftpを使用するのは、たいてい自社のサーバーとではなく、他社とのサーバーとです。注文データを取得してくるのも、こちらのデータをアップロードするのも。御存知のように、sftpはsshと同じプロトコルであり、sshが使用できるならsftpも使用可能。しかし、先のような状況だと、セキュリティのために、sftpは使用できるけれど、sshは使用できないようにサーバーが設定されています。また、sftpでは、勝手に他のディレクトリへ行ったりできないように、閲覧できるディレクトリを特定して、jailします。

このような制限された設定となると、例えば以下のように、lsコマンドの実行が不可能となり、ファイルのリストさえ取ってくることできなくなります。

Psy Shell v0.7.2 (PHP 5.6.26 — cli) by Justin Hileman
>>> use SSH;
=> null
>>> SSH::into('acme')->run(['ls data']);
[foo@acme] (acme) This service allows sftp connections only.
..

かと言って、

>>> SSH::into('acme')->list('data');
PHP Fatal error:  Call to undefined method Collective\Remote\Connection::list()

のように、listのコマンドがあるわけでもありません。

いろいろ、探ってみると、

Laravel Collective Remoteは、

https://github.com/LaravelCollective/remote/blob/5.3/src/SecLibGateway.php

では、

以下のphpseclibというパッケージを使用しています。

https://github.com/phpseclib/phpseclib/blob/master/phpseclib/Net/SFTP.php

これらを参考にすると、

>>>SSH::into('acme')->getGateway()->getConnection()->nlist('data');
=> [
     "base-invoice.csv",
     "base-product.csv",
     ".",
   ]
..

というように、nlistを実行できます。

他にも、chdirとも実行できるようです。オープンソースのおかげでこういう問題自分で解決できます。

パブリックキーを使用してsftp

中規模のECを営む私のお客さんのところでは、自社製品を持ち出荷するゆえに、自社のウェブで販売するのみではなく、他社でのウェブサイトでも製品が売られています。となると、そこからも注文データが来ます。

その発注データは、ウェブサービスを使用したAPIを使用して取得、というようなものではなく、彼らが生成した注文データをCSVファイルとして指定のサーバーに置かれ、それを毎日sftpでダウンロードして、システム内に取り込みます。

また、逆に自社サイトで販売した注文情報を、出荷や解析の目的で他のサーバーにsftpでアップロードというケースもあります。

ここで重要なのは、パブリックキーを使用したsftpのコミュニケーションが必要なことです。

そこで登場するのが、以前会員編集フォーム紹介した、Laravel Collectiveです。

Laravel Collectiveのインストール

最初に、以下をコマンドラインで実行します。

$ composer require "laravelcollective/remote":"^5.3.0"

この実行で、必要なライブラリがインストールされ、composer.jsonが更新されます。

次に、config/app.phpのファイルをエディタで開き、以下を追加します。

  'providers' => [
    // ...
    Collective\Remote\RemoteServiceProvider::class,
    // ...
  ],

  'aliases' => [
    // ...
    'SSH' => Collective\Remote\RemoteFacade::class,
    // ...
  ],

設定

SSHのパッケージがインストールされたところで、次は設定です。Laravel用に開発されたパッケージでは、たいていはパッケージ独自の設定ファイルを、config/ディレクトリに作成します。

以下を、コマンドラインで実行してください。

$ php artisan vendor:publish --provider="Collective\Remote\RemoteServiceProvider"

この実行により、config/remote.phpの設定ファイルが作成されます。

エディターで、そのファイルを開き、acmeエントリーを追加します。acmeでなくても、名前はなんでもよいです。

  'default' => 'production',

  'connections' => [
        'production' => [
            'host'      => '',
            'username'  => '',
            'password'  => '',
            'key'       => '',
            'keytext'   => '',
            'keyphrase' => '',
            'agent'     => '',
            'timeout'   => 10,
        ],

        'acme' => [
            'host'      => env('ACME_HOST'), // sftp先
            'username'  => env('ACME_USERNAME'), // ログイン名
            'password'  => '', // 
            'key'       => base_path(env('ACME_KEY')), // パブリックキーファイルのパス名',
            'keytext'   => '',
            'keyphrase' => '', 
            'agent'     => '',
            'timeout'   => 10,
        ],
    ],

見ての通り、このファイルにはデフォルトとして、すでにproductionがありますが、それには触らず、ここではacmeの配列を新設しました。開発サイトでのテストも考えて、いろいろな情報をここで設定できるのは便利です。

今回は、バージョン管理にプライベートの値が入らないように、.envで指定するようにしました。特に、大きい値のパブリックキーを含むファイルを、keyで指定できるのも便利です。このファイルは、.gitignoreで必ずバージョン管理から排除が必要ですね。

keyphraseは、パブリックキーを生成したときに指定したパスワードを入れますが、パスワード指定なしで作成することが多いです。それでも十分セキュアであるのでここでは空とします。

.envでは、以下のような変数を追加します。

..
ACME_HOST=foo.google.com
ACME_USERNAME=foo
ACME_KEY=config/keys/test.pem
..

以上でテスト可能となります。もし、sftp先でsftpのみで設定されていないなら、

$ php artisan tinker

を実行して、接続テストができます。以下では、sshでacmeに接続して、lsコマンドを実行しました。

Psy Shell v0.7.2 (PHP 5.6.25 — cli) by Justin Hileman
>>> use SSH;
=> null
>>> SSH::into('acme')->run(['ls']);
[foo@acme] (acme) data.csv
=> null
>>> 

sftpでファイルのダウンロード・アップロード

ここまで来たら、あとは簡単です。プログラムの中で以下を実行するだけです。

ファイルのダウンロードは、

SSH::info('acme')->get('data.csv', storage_path('data.csv'));

ファイルのアップロードは、

SSH::info('acme')->put(storage_path('data.csv'), 'data.csv');

あたかも、コマンドラインで実行するような軽さですね。

Top