hkのweblog

ひよっこエンジニアがにわとりになるまでの軌跡

コメントアウトに惑わされるな

結局今月も1回しか記事を書いていないことに気付き、実家で記事を書く大晦日です。
今日はほんと小ネタですが一応。

しばらく前、仕事で古めのページのjspを書き換えていた時、コメントアウトに翻弄されて1時間ほど作業が止まったことがありました。

ハマっていたのはこれです。

<html>
    <head>
        <title>test page</title>
        <script>
            <!--
                (function() {
                    console.log("非コメントアウト");
                })();
                //-->
            var nextYear = 2018;
            (function() {
                    console.log(nextYear + "年まであと少し!");
                })();
        </script>
    </head>
    <body>
        <p>test</p>
    </body>
</html>

ちょっとこの画面では分かりづらいのですが、普段私が業務で使っているEclipseでこのソースを開くと、scriptタグ内の<!--//-->に囲まれた部分が全て薄いブルーになります。
私は<!--//-->に囲まれた部分は実行されないだろうと思って読み飛ばしていたのです。
が、このhtmlを開くとコンソールはこうなります。

非コメントアウト
2018年まであと少し!

つまり一見コメントアウトされているように見える部分も実行されるのです。
これはjavascriptが無効にされているブラウザへの対応だそうで、<!--//-->に囲まれた部分が画面に表示されてしまうことを防ぐためのコメントアウトだそうです。

このことを知らなかった私は、約1200行の所々にscriptタグが挿入され、その一部がコメントアウトされていることを確認し、「これがコメントアウトされているのになんでこの挙動になるんだろう…?どこか他の箇所でjsを読み込んでいる?それともこれはサーバーサイドで実装している動き…?」などと1時間ほど悩みました。

今時javascriptを無効にしているユーザーはあまりいないように思いますが、古くから残っているソースコードにはこういう書き方が多く残っているのかもしれませんね。

今年はずいぶん初歩的なことばかり書いてきましたが、来年はもっと高度なブログも書いていきたいと思います。では良いお年を。

Laravel 5.5 + Vue 2.1でSPA的なものを作っている話のつづき

 少し間が空きましたが、前回の記事の続きです。
前回は複数のモデルのリレーションを設定して、jsonを返してやるところまで進めました。これですね↓

{
    sento_code: 1,
    sento_name: "テルマー湯",
    address: "東京都新宿区歌舞伎町1-1-2",
    tel: "03-5285-1726",
    sento_map: {
        sento_code: 1,
        lat: 35.694535,
        lng: 139.705160
    }
}

今回は返ってきたjsonを展開してみます。

Laravel側で最初に読み込むviewを設定する

 Single Page Applicationは文字通り最初のリクエスト時に1つのページを返しますが、以降はhtmlを返さず同じページを使いまわします。
APIにリクエストを投げて、返ってきたjsonを基にページを書き換えていくようなイメージになります。
今回の場合、最初のリクエスト時にベースとなるページを返したり、モデル周りなどサーバーサイドの処理を司るのがLaravel、以降ページを書き換えたりルーティングをしたりするのはVue側の役割になります。

というわけでまずはweb.phpに最初に読み込むページを設定します。

<?php

Route::get('/{any}', function () {
    return view('app');
})->where('any', '.*');

ざっくり言うと、「スラーの後に何が来ようとapp.blade.phpを返すよ」ということを書いています。

次に読み込まれるapp.blade.phpが以下です。

<!DOCTYPE html>
<html lang="{{config('app.locale')}}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X=UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <title>銭湯まっぷ</title>
        <link rel="stylesheet" href="{{mix('css/app.css')}}">
        <script>
            window.Laravel = {
                csrfToken: "{{csrf_token()}}"
            };
        </script>
    </head>
    <body>
        <div id="app">
            <navbar></navbar>
            <div class="container">
                <router-view></router-view>
            </div>
        </div>
    </body>
    <script src="{{mix('js/app.js')}}"></script>
</html>

静的ファイルの読み込み方とかcsrfがどうだとか細かく説明すべき箇所はたくさんあるのですが、今回は端折ります。
重要な役割を担うのは最後のほうで読み込んでいるapp.jsです。こいつが<div id="app">の中を書き換えます。

ルーティングの設定

import Vue from 'vue';
import VueRouter from 'vue-router';

window.Vue = require('vue');

require('./bootstrap');

Vue.use(VueRouter);

Vue.component('navbar', require('./components/Layouts/Navbar.vue'));

const router = new VueRouter({
    mode: 'history',
    routes: [
        {path: '/', component: require('./components/Index.vue')},
        {path: '/about', component: require('./components/About.vue')},
        {path: '/detail/:sento_code', component: require('./components/Detail.vue')},
    ]
});

const app = new Vue({
    router
}).$mount('#app');

私自身、SPAについて何も知らなかった頃は「SPAってURL固定でどうやってAPIリクエストするんだろう…」と思っていましたが、当然ルーティングはあります。
vue-routerというのが必要になるので予めpackage.jsonに書いてnpm installしておきましょう。
今回は'/'を叩くとIndex.vueを読み込み、'/about'を叩くとAbout.vueを読み込む…という構造にしています。
3つめだけ少し勝手が違いますが、'/detail/1'を叩くとsento_code=1というパラメータを渡しつつDetail.vueを読み込みます。 この他にコンポーネントを作っている部分があります。こんな書き方で<navbar></navbar>のところにNavbar.vueを読み込むことができます。
説明の順番がおかしい感じもしますが、最後の3行でVueインスタンスを生成しています。これで<div id="app">の中を書き換えることができます。

Vueのテンプレートの中でjsonを読み込む

さて、ようやく本題。Vueのテンプレートの中でjsonを読み込みます。
いくつかvueを読み込んでいますが、今回はこの中でDetail.vueについて書きます。
先程のjsonを読み込んで銭湯の情報の詳細ページを作るイメージです。
シンプルな構造のjsonのサンプルなら結構転がっているのですが、ちょっと階層が深くなると全然出てこなくて苦労しました…

<template>
    <div>
        <table>
            <tr>
                <th>施設名</th>
                <td>{{sento.sento_name}}</td>
            </tr>
            <tr>
                <th>住所</th>
                <td>{{sento.address}}</td>
            </tr>
            <tr>
                <th>電話番号</th>
                <td>{{sento.tel}}</td>
            </tr>
            <tr>
                <th>緯度</th>
                <td>{{sento.sento_map.lat}}</td>
            </tr>
            <tr>
                <th>経度</th>
                <td>{{sento.sento_map.lng}}</td>
            </tr>   
        </table>
    </div>
</template>

<script>
export default {
    created() {
        this.getSento()
    },
    data() {
        return {
            sento: {
                sento_name: '',
                address: '',
                tel: '',
                sento_map: {
                    lat: '',
                    lng: '',
                }
            }
        }
    },
    methods: {
        getSento() {
            this.$http.get('/api/sento/' + this.$route.params.sento_code)
            .then(res => {
                this.sento = res.data
            })
        }
    }
}
</script>

まず、<script>以下から見ていきます。
このへんは疎いのですが、export defaultはimportする際に特に指定がなければそのクラスや関数を呼ぶということのようです。
今回はgetSentoを呼んでいます。
下の方にgetSentoを書いています。'/api/sento/'のうしろにthis.$route.params.sento_codeとありますが、これが先ほどvue-routerで書いたパラメータです。
jQueryなんかと似ていますが、帰ってくるres.dataをsentoに渡してやって、これをテンプレートの中で展開していきます。
dataの部分では帰ってくるオブジェクトの型を書いています。主に苦労したのはここの構造だったりします。
cssを全く書いていないので表示はぐちゃぐちゃですが、表示されてほしいものはこれで表示されるはずです。
Vue.jsはまだまだ勉強中ですが、シンプルかつ便利な機能が豊富に準備されていて、おしゃれなアニメーションとかも作りやすそうです。
全体的に駆け足になりましたが、今日はこんなところで。

Laravel 5.5 + Vue 2.1でSPA的なものを作っている話

 月2回更新を目標としながら丸1ヶ月ブログの更新をサボってしまいました…
気を取り直して更新を再開していきます…

 最近、個人的にLaravelとVue.jsを組み合わせてSPA的なサイトを作っています。
LaravelもVue.jsも初めて扱うのでそこそこ苦労していますが、新しい技術を学ぶのはやっぱり楽しいものです。
以前、「Vagrant上にLaravelの開発環境を作る」という記事を書きましたが、これからしばらくはLaravelとVue.jsでサイトを作りながら気付いたことや学んだことを書いていこうと思います(たぶん他のことも書くけど)。

 今日は手始めにLaravelのmodelのリレーションあたりについて書いてみようと思います。

modelの生成

まずはmodelを作るところからスタートです。
DBにあらかじめ以下のような2つのtableが用意されているとします。
銭湯の基本情報と銭湯の位置情報の2つのテーブルです。1件のレコードに対して1件のレコードが対応する最もシンプルなhasOneのパターンです。

sento_info←銭湯の基本情報

カラム名 長さ データ例 備考
sento_code int 7 1 primaryKey
name varchar 50 テルマー湯
address varchar 100 東京都新宿区歌舞伎町1-1-2
tel varchar 15 03-5285-1726

sento_map←銭湯の位置情報

カラム名 長さ データ例 備考
sento_code int 7 1 sento_info.sento_codeのforeignKey
lat double 10,6 35.694535
lng double 10,6 139.705160

これもLaravelのmigrateで作ればOKですが、長くなるので今回は割愛します。 モデルは職人さんが作ってくれます。

php artisan make:model SentoInfo

同様にSentoMapも作ります。
なお、デフォルトではappのすぐ下にmodelが生成されるのですが、以下のようにパスを追加してやるとちゃんとフォルダの下に入れてくれます。

php artisan make:model Models/SentoMap

modelの修正

次に生成したモデルをDBと紐付ける必要があります。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class SentoInfo extends Model
{
    protected $table = 'sento_info';
    protected $primaryKey = 'sento_code';

}

鍵となるのは最後の2行です。
Laravelはデフォルトではテーブル名としてmodel名の複数形(この場合はsento_infos)、primaryKeyとしてidを指定してくれます。
今回のように別名を付けている場合は必ずこのように明示してやる必要があります。

SentoMap.phpの方も同じようにDBと紐付けておきます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class SentoMap extends Model
{
    protected $table = 'sento_map';
    protected $primaryKey = 'sento_code';
    
    }
}

リレーションを設定する

ようやく本題のリレーションの設定です。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class SentoInfo extends Model
{
    protected $table = 'sento_info';
    protected $primaryKey = 'sento_code';
    public function sentoMap() {
        return $this->hasOne('App\Models\SentoMap', 'sento_code');
    }
}

こんな感じ。hasOneの第1引数に紐付けるモデルのパス、第2引数にforeignKeyを設定します。
特にforeignKeyの方を忘れるとControllerから引っ張る時に正しいクエリを生成してくれなくてエラーになります。

Controllerから呼び出してみる

まずはapi.phpの方にルーティング情報を書いてみます。

<?php

use Illuminate\Http\Request;

/* 中略 */

Route::group(['middleware' =>'api'], function() {
    Route::get('sento/{sento_code?}', 'SentoController@detail');
}) ;

今回はルーティングが本題ではないので詳しく書きませんが、/api/sento/2 とかを叩くとsento_code=2でAPIが返ってきます。
このAPIをVue側で取得してテンプレートに埋め込んで表示するわけですが、そのあたりはまた次回にでも書こうと思います。

次にSentoController.phpです。コントローラーも職人さんが生成してくれます。

php artisan make:controller SentoController

で、生成されたSentoController.phpにdetailメソッドを書き加えます。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\SentoInfo;

class SentoController extends Controller
{
  public function detail($sento_code) {
    $sento = SentoInfo::with('sentoMap')->find($sento_code);
    return $sento;
  }
}

with('リレーション名')はeager loadingと呼ばれるもので、SentoInfoをロードする段階でSentoMapも先に取得してしまう処理を指しています。
このやり方をしないとsento_mapの列の数だけクエリが発行されることになるため無駄な負荷がかかってしまうようです。
今回は1対1のhasOneなのでこだわる必要性は薄いかもしれませんが、スピード面やクエリの発行回数で課金されるような状況を考えると、このやり方をしておいた方が良さそうです。

find(primaryKey)でそのprimaryKeyを持ったレコード1件を取得できます。
さて、 /api/sento/1を叩いてみると…

{
    sento_code: 1,
    sento_name: "テルマー湯",
    address: "東京都新宿区歌舞伎町1-1-2",
    tel: "03-5285-1726",
    sento_map: {
        sento_code: 2,
        lat: 35.694535,
        lng: 139.705160
    }
}

こんな感じでjsonが返ってくれば成功です。
今日はとりあえずこんなところで。次回は返ってきたjsonをVue.jsの方で扱う記事でも書きたいと思います。 →次の記事はこちら

Cookieに複数の値を格納したい時に

1つのCookieに複数の値を入れたい、配列を入れたい、そんな時に使える便利な方法をメモしておきます。

こいつを使います↓
https://github.com/carhartl/jquery-cookie

基本的な使い方

例えば、連想配列Cookieに格納する場合を想定してみます。

//以下でCookieをjson化する
$.cookie.json = true;

//sampleHashという連想配列をsample_cookieというCookieにjson形式で保存する
let sampleHash = {fruit: 'apple', vegetable: 'tomato'};
$.cookie('sample_cookie', sampleHash);

//json形式で保存された'sample_cookie'をパースして連想配列に戻す
let parsedHash = $.cookie('sample_cookie');
console.log(parsedHash.fruit);
// apple

実にシンプルです。
1つのサイトで持てるCookieの数には上限があるので、使い所はあるのかなと思います。
その一方で1つのCookieの容量は4096byteが上限となっているので、いたずらにCookieを膨らませていくとオーバーしてしまうので注意が必要です。
短いですが今日はこんなところで。

awkコマンドを使ってログを調べる

先日、自分のリリースしたものが派手に障害を起こしてしまい、影響範囲を調べるためにエラーログをあれこれいじる機会がありました。
こういう機会は無いに越したことはないのですが、その時に活躍したawkコマンドについて簡単にまとめておきます。

awkとは

awkは厳密にはコマンドではなく、それ自体がスクリプト言語のようです。単一のテキスト(ファイル)を整形したりするのに便利なツールです。
シェルスクリプトといえばPerlが思い浮かびますが、そこまでの学習コストを必要とせず、簡単にフィルタリング処理を書けるのがawkの特長です。


基本的な使い方

例えば以下のようなテキストファイルがあるとします。
sample1.txt

hoge fuga foo bar

とりあえず、sample.txtからfugaを取り出してみます。

awk '{print $2}' sample1.txt
// fuga

このコマンドでスペース区切りの2番目の要素を取り出すことができます。
他にも色々できます。例えば正規表現を使ってみます。
まずこんなテキストファイルがあるとします。
sample2.txt

hoge fuga
hello world
test dayo

この中からhが含まれる行だけを取り出してみます。

awk 'match($0, /h.*/)' sample2.txt
// hoge fuga
   hello world

第1引数に$0を指定していますが、これで全要素を指定することができます。
第2引数は見ての通りhから始まる要素を取り出す正規表現です(hは途中でもOKです)。
awkの使い方は他にも色々あるのですが、キリがないのでこの記事では割愛します。


ログから会員番号を抽出してみる

どのようなデータをログに残しているかはシステムやサービスによって当然異なるとは思うのですが、ログをちょっと眺めてawkコマンドを活用すれば、膨大なログの中から必要な情報を抜き出すことができます。
自分の場合、障害にかかって画面を表示できなかったサイト会員がどれくらいいるのか調べる必要がありました。
出力されていたエラーログは大体こんな感じでした。 sample_error.log

ERROR~~~~~~(中略) /error/no/genninn.jspが存在しません。 ~~~(中略)~~~memberCode=213122~~~(中略)

だいぶ端折ってしまいましたが、こんなログ大量に出力されていました。
この中には今回の障害には関係ないものも含まれているので、今回の障害に関連したものだけを抽出してみます。

grep ERROR sample_error.log | grep genninn.jsp | awk '{print $15}' | sort | uniq
///memberCode=111112
   memberCode=111133...

awkも他のコマンド同様、パイプで繋いで使うことができます。
まず最初にgrepコマンドで対象ファイルの中で「ERROR」を含む行を取得。さらに対象ファイルの名前を含む行を抽出します。ここでようやくawkが登場。各行の中からmemberCodeを含む要素を抽出しています(memberCodeはスペース区切りで15番目でした)。
sortは文字通りのソート、繰り返しエラーにはまっている会員の重複を排除するのにuniqを使っています。
当時の稼働系サーバーは一桁の台数だったので、各サーバーに入って同じコマンドを叩き、出てきたものをsort|uniqしてまとめました。
ここでさらにawkを活用してやれば「memberCode=」の部分を削るなんてこともできそうですね。
結果…
実に100以上の会員がgenninn.jspを表示できずに困っていたことが判明しました。
我ながら本当に酷いですね…
悔い改めつつ今日はこんなところで。

Vagrant上にLaravelの開発環境を作る

2年以上前にフレームワークを使わずにPHPで書いたサイトがあるのですが、久しぶりにAnalyticsを見たらまだ日に30PVくらいのアクセスがありました。
今ならもっと良いサイトにできそうな気がしたので大規模改修をしようと思い立ち、Laravelベースで再実装することにしました。
というわけで早速Laravelの開発環境を作ったので、経過をまとめておきます。

最初にやっておくこと

Homesteadのvagrant boxを追加

そもそもHomesteadとは

ざっくり言えば、Laravelの開発環境をまとめたBoxです。 Homesteadの公式サイトによれば、

Homesteadは、WindowsMacLinuxシステムで実行でき、Nginx Webサーバー、PHP7.0、MySQL、Postgres、Redis、Memcached、Nodeやその他、素晴らしいLaravelアプリケーションを開発するために必要な、クールなツールを全部含んでいます。

だそうです。
これさえ入れておけば大体は事足りそうです。

VagrantにHomesteadを入れる

vagrant box add laravel/homestead

途中でどの環境に入れるのか聞かれるので、virtualboxを選択します。そこそこ時間がかかります。

Homesteadのダウンロード&初期化

任意のフォルダに移動し、git cloneします。

git clone https://github.com/laravel/homestead.git Homestead

git cloneしたら新しくできたディレクトリ(Homestead)に入って、以下のコマンドをたたきます。

bash init.sh

これでHomestead内にHomestead.yamlという設定ファイルができるはずです。

設定ファイルの書き換え

Homestead.yamlの書き換え

先ほどできたHomestead.yamlを書き換えます。最初は以下のような状態のはずです(バージョン次第で違うかも)

---
ip: "192.168.10.10"
memory: 2048
cpus: 1
provider: virtualbox

authorize: ~/.ssh/id_rsa.pub

keys:
    - ~/.ssh/id_rsa

folders:
    - map: ~/code
      to: /home/vagrant/code

sites:
    - map: homestead.app
      to: /home/vagrant/code/public

databases:
    - homestead

# blackfire:
#     - id: foo
#       token: bar
#       client-id: foo
#       client-token: bar

# ports:
#     - send: 50000
#       to: 5000
#     - send: 7777
#       to: 777
#       protocol: udp

このうち、書き換えるべきはfoldersとsitesの2項目です。SSHキーを作る場合も変更が必要ですがこの記事では割愛します。

foldersはvagrant上のディレクトリにローカルディレクトリをマウントする設定です。書き換え方は以下のような感じ。

folders:
    - map: ~/[ローカルでプロジェクトを作る場所]/[これから作るプロジェクト名]
      to: /home/vagrant/code

次にsitesです。こちらはサイトとして公開するディレクトリの設定です。

sites:
    - map: homestead.app
      to: /home/vagrant/code/[これから作るプロジェクト名]/public

mapのところは任意のものに変えても大丈夫みたいです。

hostsの書き換え

macの場合、/etc/hostsを書き換えます。windowsでは場所が違うみたいです。
自分の場合、ルート権限が必要だったのでsudo vi hostsで書き換えました。

192.168.10.10   homestead.app

先ほどHomestead.yamlに設定したmapと連動するので、IPと名前は合わせておきます。

vagrantの起動とプロジェクト作成

vagrantの起動

先ほどHomesteadをgit cloneしたディレクトリに移動してvagrant upします。
ちゃんと起動すればvagrant sshvagrant環境に入れます。

プロジェクト作成

vagrant環境に入ったらcodeディレクトリに移動し、以下のコマンドでLaravelのプロジェクトを作成します。
Composerを使うんですね。

composer create-project laravel/laravel --prefer-dist [任意のプロジェクト名]

--prefer-distは安定版を入れるというオプションだとか。(Composerの使い方よく知らない…)

Laravelが導入できたか確認する

方法は簡単です。vagrantを起動した状態でHomestead.appにアクセスして、こんな画面が表示されれば成功です。 f:id:h2r4t:20170923233312p:plain なお、うまくいっていない時は「no input file specified」と表示されます。
おそらくHomestead.yamlかhostsの設定の問題なので、このあたりをよく確認しましょう。
おつかれさまでした。
さてさて、Laravelは素人ですが、これからいい感じのサイトを作れたらいいなあ。

【無料】Kindleストアで見つけたAngular4のおすすめ書籍

今日は最近Kindleで見つけたAngular4のおすすめ書籍を紹介しようと思います。しかも無料…!

Kindleストアでは技術系でも無料の書籍を時々見かけます。ただ、買ってはみたもののクオリティはお察し…というのがいつものパターン。
と、私も思っていたのですが、今回、無料とは思えない良書を見つけました。
それがこちら↓

英語の本ではあるのですが、エンジニアたるもの少々の英語は読めたほうがいいですよね。
今のところ日本人によるレビューは一つもついていないのですが、Amazon.comのレビューは29件で平均4.5くらい。
分量も十分です。この本、普通に数千円で売っていいレベルだと思います。

この本はAngular4の技術書で、簡単なものから始まって少しずつAngularでアプリケーションを作っていく流れになります。 Angularといえば、2系からはTypeScriptについての理解も必要となり、構造そのものを理解するにも苦労するイメージがありました。
ただ、この本はZone.jsなどのモジュールがどのようにアプリケーションを動かしているのか、コンパイルとトランスパイルの違いまで説明があり、かなり分かりやすく納得感がある印象でした。

また、特徴的なところとして、Plunker上で開発を進めている点が挙げられます。
Plunkerは、ブラウザ上でコードを書いて実行できるツールで、コードを共有することもできます。あまり馴染みのない人も多いかもしれません。

Plunkerを使えば、面倒な開発環境構築が必要なく、お手軽にAngularの開発を楽しむことができます(とはいえブラウザ上では限界があるので本格的にやるなら自前で環境を用意したほうがいいとは思います)。
この本では、段階ごとにサンプルコードがPlunkerで共有されており、確認することができます。
ですので、ブラウザを開きながら、PC版のKindleで読むのがおすすめですね。

実はかく言う私もまだ10%くらいしか読めていないので()、これからどんどん読み進めていきたいところです。