技術的な話題 に関する投稿を表示しています

NGINX Unit なるものリリースされたらしいのでとりあえず PHP でも動かしてみる

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

NGINX Unit なるものがリリースされたらしい。

・NGINX 公式サイトの情報
NGINX Unit

・NGINX Unit 公式サイト
NGINX Unit

・見かけた記事
NGINXからアプリケーションサーバ「NGINX Unit」がオープンソースで登場。PHP、Go、Pythonに対応。Java、Node.jsにも対応予定 - Publickey

ドキュメントに書かれているインストール方法や設定を見てたらパッと出来そうだったので思いたってやったったの巻。

どうにも公式サイトのドキュメンテーションを見ると CentOS と Ubuntu 向けにはパッケージが適当されているっぽい。なのでコレを利用する。また nginx が既にいる環境でやると便利そうなので docker で立ち上げていく

// ホストにて
$ docker run --name nginx-unit -d -p 8080:80 nginx
$ docker exec -it nginx-unit bash

// 以降はコンテナ内、ゲストで作業

// 準備
# apt-get update
# apt-get install curl net-tools vim less

// キーの登録
# curl http://nginx.org/keys/nginx_signing.key > /tmp/nginx_signing.key
# apt-key add /tmp/nginx_signing.key

// /etc/apt/sources.list に追記
# vim /etc/apt/sources.list
deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx

// nginx-unit のインストール
# apt-get update
# apt-get install unit


// コンフィグ確認
# cat /etc/init.d/unit
CONFIG=/etc/unit/config

// /var/run/control.unit.sock が設定用のソケットファイルっぽい
    dumpconfig)
        curl -sS --unix-socket /var/run/control.unit.sock localhost >${CONFIG}.new



# vim /etc/unit/config
{
     "listeners": {
         "*:8300": {
             "application": "myapp"
         }
     },
     "applications": {
         "myapp": {
              "type": "php",
              "workers": 3,
              "root": "/var/www/html/",
              "index": "index.php"
         }
     }
}

// 起動
# service unitd start

// コンフィグの確認
# curl --unix-socket /var/run/control.unit.sock http://localhost/
{
        "listeners": {},
        "applications": {}
}

// コンフィグの変更してみる
# service unitd stop
# mv /etc/unit/config /etc/unit/myconfig.json
# service unitd start

# curl -X PUT -d @/etc/unit/myconfig.json --unix-socket /var/run/control.unit.sock http://localhost/
curl: (52) Empty reply from server

# curl --unix-socket /var/run/control.unit.sock http://localhost/

// レスポンスが返ってこない

// ログ確認
# less /var/log/unitd.log

// ... とくにエラーっぽいものなし。

パッケージのものだとうまく動かない???

ということでソースから入れる路線を試す。

// パッケージからのものを消す
# apt-get remove unit

// 準備
# apt-get install git build-essential php php-dev libphp-embed
# cd /tmp
# git clone https://github.com/nginx/unit
# cd unit

// ビルド
# ./configure --prefix=/usr/share/unit/
# ./configure php
# make all

// インストール
# make install
# ln -s /usr/share/unit/sbin/unitd /sbin/unitd

// 起動
# unitd

// 設定してみる
# vim /tmp/unit_config.json

{
     "listeners": {
         "*:8300": {
             "application": "myapp"
         }
     },
     "applications": {
         "myapp": {
              "type": "php",
              "workers": 3,
              "root": "/var/www/html/",
              "index": "index.php"
         }
     }
}


# curl -X PUT -d @/tmp/unit_config.json --unix-socket /usr/share/unit/control.unit.sock http://localhost/
{
        "success": "Reconfiguration done."
}

# curl --unix-socket /usr/share/unit/control.unit.sock http://localhost/
{
        "listeners": {
                "*:8300": {
                        "application": "myapp"
                }
        },

        "applications": {
                "myapp": {
                        "type": "php",
                        "workers": 3,
                        "root": "/var/www/html/",
                        "index": "index.php"
                }
        }
}

// 動作確認
# echo "<?php echo 'hello php';" > /var/www/html/index.php
# curl localhost:8300
hello php

// nginx からプロキシしてみる
# vim /etc/nginx/conf.d/default.conf

upstream unit_backend {
    server 127.0.0.1:8300;
}

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /var/www/html;
        index  index.html index.htm;
    }

    location ~ \.php$ {
        proxy_pass http://unit_backend;
        proxy_set_header Host $host;
    }
}

// 動作確認
# service nginx reload
Reloading nginx: nginx.

# curl localhost/index.php
hello php

出来ているようなので、もう少しちゃんと PHP を動かしてみる。

// phpモジュール追加
# apt-get install php-mbstring php-zip

// composer 導入
# curl https://getcomposer.org/installer > installer
# php installer
# rm installer
# chmod +x composer.phar

// Laravelプロジェクトインストール
# ./composer.phar create-project laravel/laravel app

// Laravel プロジェクトの色々設定
# chmod -R 777 /var/www/html/app/storage/
# php artisan key:generate

// nginx-uniti のルートディレクトリを変更
#  curl -X PUT -d '"/var/www/html/app/public/"' --unix-socket /usr/share/unit/control.unit.sock http://localhost/applications/myapp/root
{
        "success": "Reconfiguration done."
}

// 確認
# curl --unix-socket /usr/share/unit/control.unit.sock http://localhost/
{
        "listeners": {
                "*:8300": {
                        "application": "myapp"
                }
        },

        "applications": {
                "myapp": {
                        "type": "php",
                        "workers": 3,
                        "root": "/var/www/html/app/public/",
                        "index": "index.php"
                }
        }
}


// nginx のルートディレクトリを変更
# vim /etc/nginx/conf.d/defualt.conf

    location ~ / {
        proxy_pass http://unit_backend;
        proxy_set_header Host $host;
    }

# service nginx reload


// 動作確認
# curl localhost

// 何か出ていそうなのでブラウザで開く…!

イエーイ! NGINX Unit で Laravel アプリケーションが動いたぞ~~~空っぽだけど…。

ご覧とおり php-fpm は動いてなくて unit のワーカーが動いてるだけですねー、しゅごい。

# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  32648  3464 ?        Ss   00:50   0:00 nginx: master process nginx -g daemon off;
root         8  0.0  0.0  18224  2176 ?        Ss+  00:51   0:00 bash
root     26019  0.0  0.0  22196   852 ?        Ss   03:00   0:00 unit: main [unitd]
nobody   26021  0.0  0.0  32440   736 ?        S    03:00   0:00 unit: controller
nobody   26022  0.0  0.0 204620  1440 ?        Sl   03:00   0:00 unit: router
nobody   27293  0.0  0.7 269004 28232 ?        S    03:28   0:00 unit: "myapp" application
nginx    27330  0.0  0.0  33088  2152 ?        S    03:31   0:00 nginx: worker process
root     27331  1.2  0.0  18140  2064 ?        Ss   03:41   0:00 bash
root     27337  0.0  0.0  36572  1588 ?        R+   03:41   0:00 ps aux

現状では Go / PHP / Python に対応しているようで言語環境の準備と configure 時に言語指定すれば使えるようです。

手元にいい感じにお試せるアプリケーションがいないので細かい挙動の様子をみるなど出来ていないのですが、リリースニュース直後からはてぶが盛り上がったり github も盛り上がりを見せている(ように見える)ので、ちょっとしたバグなどなどあってももりもり直されていくんじゃないかなーという予感です。

そのうち Apache + mod_php / nginx + php-fpm / nginx + unit などでパフォーマンスも比べていきたいですねー!

Python で階層型クラスタリング

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

最近は Python をちょこまかといじることが増えてきたように感じています。というところで、クラスタリングのことを調べていたのですが、なんとなく k-means しておけばいいっしょ、くらいだったのですが、もっと奥深い色々が出てきて、クラスタリングわからないマンになりつつあります。そんなわけで、階層型クラスタリングを調べて、やってみたのでまとめておきます。

 

クラスタリングとは

クラスタリングとはデータを良い具合にまとめるという、教師なし学習の典型例です。何かしらの特徴量に沿って、データの流れや雰囲気をアルゴリズム的に見抜き、その様子でデータをまとめることができます。

クラスタリングとしてもっとも一般的なものは k-means でしょうか。k-means は非階層型クラスタリングと呼ばれています。具体的には以下のようなアルゴリズムでクラスタリングを行います。

  1. 事前に設定されるクラスタ数 K を元に、ランダム(※)にクラスタの中心を決定する
  2. 各要素から一番近いクラスタの位置を探し、それを各要素のクラスタとして設定する
  3. 各クラスタの重心を計算し、その重心位置をクラスタの中心として設定する
  4. 各要素から一番近いクラスタの位置を探し、それを各要素のクラスタとして設定する
  5. 各クラスタの重心を計算し、その重心位置をクラスタの中心として設定する
  6. ...これを重心の移動がしきい値を下回るまで、あるいは指定の回数を超えるまで実施する
  7. 最終的に、各要素から一番近いクラスタの位置が、その要素のクラスタとして決定される

(イメージ図@雑)

※ランダムにするか、程よい位置にするか(k-means++)などの初期位置決定の方法があります

後述する階層型との比較でも挙げますが、初期位置によってある程度クラスタの別れ方が分岐することも往々にして発生します。また、クラスタリング結果が毎回異なる可能性もあり、厳密な分類がほしい、なんとなく纏めるでは嫌、といった場合の要望をかなえることはできません。かといって階層型クラスタリングがこの要望を常に叶えられるわけでもないのですが...

今回取り上げる階層型との比較として後述しますが k-means では、事前にクラスタ数を設定する必要があるため、それが難しいような状況では大きな力を発揮することが難しいです。

ちなみにアルゴリズムとして x-means という、クラスタ数を自動で設定できるものもあります。K=2 として再帰的に k-means を実施し BIC と呼ばれるベイズ情報量規準が、設定したしきい値を下回るまで実行していくようなものになっています。詳しいことはぼくもあまりわからないので、その話が上がっている論文を見ると幸せになれると思います。

http://www.rd.dnc.ac.jp/~tunenori/xmeans.html

機械学習について触れていると、似たような「クラス分類」といったワードを見かけたこともあるかと思います。こちらはクラスタリングとは異なり、教師あり学習で「あるデータがどのクラスに属しているかを当てる」という全く別の問題になります。リンゴは果物、キャベツは野菜、というような、明確に正解があるようなデータを分類していくときには、クラス分類になります。

 

階層型クラスタリングとは

階層型クラスタリングは以下のようなアルゴリズムで処理されます。

  1. すべての要素に固有のクラスタを設定する
  2. クラスタを2つ取り上げていき、最も距離が近いもの(※)をくっつけて、新しいクラスタを作成する
  3. これを再帰的に実行し、最終的にクラスタが1つになるまで実施する

※「距離」の定義は設計次第です。ユークリッド距離、コサイン距離、など様々な計算方法があります。

※くっつけて出来上がったクラスタ間の距離計算は設計次第です。

  • クラスタが含む要素をそれぞれ比較し、最も近いものを距離とする(最近隣法)
  • クラスタが含む要素をそれぞれ比較し、最も遠いものを距離とする(最遠隣法)
  • クラスタが含む要素をそれぞれ比較し、平均的な距離とする(平均法)
  • クラスタが含む要素をそれぞれ比較し、それぞれの距離について分散を利用して程よい値を取る(ウォード法、よく使われているらしい)

(イメージ図@雑)

そうして出来上がったクラスタリング結果ですが、このままでは木構造のようになっており、データとして扱うにはどれがどういうクラスタに属しているのかを明確に決定しにくいです。実際にあるタスクで利用したときには、最大距離の7割を基準にして、クラスタを分割していました。状況によってはパラメータの調整する必要がありそうな予感がしています。

計算量と実際にかかる時間を考慮すると、既に説明した k-means や、この階層型クラスタリングが高速に処理でき、「それっぽい結果」が得られるので、解決したい問題にも寄ってくるとは思いますが、クラスタリングにおける選択肢として現実的なのかなあと思います。

 

階層型・非階層型(k-means)のそれぞれの使いどころ

階層型が活用できそうなところは以下のようなところではないでしょうか?

  • データセットのうち、それぞれの距離に何かしらの特徴がありそう
  • どれくらいのクラスタに分けたらいいかわからない

階層型の場合。クラスタ間の距離で並べ替えることも容易にできるはずなので、しきい値の設定が小さいせいでクラスタを分けすぎてしまっても、ある程度までは前後関係もそれっぽく追いついてきます。なので、出来上がった後に、人力でカバーすることも可能なのではないかなあと思います(それってどうなの感はありますが...)あるいはクラスタ数がいくつになるか想定できないような場合にも、全てのデータを一度まとめあげるので、自動で出てきた結果の様子を見つつ、手動でクラスタ分けしていくことができると思います。

一方で非階層型...もとい k-means の活用できそうなところは以下のようなところではないでしょうか?

  • データセットの雰囲気がわからないけど、いったん分割してみたい
  • 明確に K 個の分類をしたい

k-means では最初に K を決定しないといけないので、どうあがいても K 個のクラスタができあがります。この分ける数が重要になるような、クラス分類に近いようなものの場合であれば k-menas は非常に有効なのではないでしょうか。ただし、それぞれのクラスタ間で相関がないことや、中途半端な位置のデータはどちらとも言えないような結果になることは注意がいるかもしれません。

また、どんなデータを入れても、なんとなくまとめてくれるので、人力で確認したときも、「ああーこういう見方もあるのね、なるほど」という謎の納得感が得られやすいとも思います。

 

階層型クラスタリングを Python でやってみる

それでは Python で 階層型クラスタリングをやっていきます。ここでは sciki-learn に入っているアイリスデータセットを対象に pandas で読み込んで scipy.cluster.hierarchy を使っていきます。

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
from sklearn.datasets import load_iris

# iris データセットの読み込み
iris = load_iris()
iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)
iris_labels = list(map(lambda t: iris.target_names[t], iris.target))

# 階層型クラスタリングの実施
# ウォード法 x ユークリッド距離
linkage_result = linkage(iris_df, method='ward', metric='euclidean')

# クラスタ分けするしきい値を決める
threshold = 0.7 * np.max(linkage_result[:, 2])

# 階層型クラスタリングの可視化
plt.figure(num=None, figsize=(16, 9), dpi=200, facecolor='w', edgecolor='k')
dendrogram(linkage_result, labels=iris_labels, color_threshold=threshold)
plt.show()

# クラスタリング結果の値を取得
clustered = fcluster(linkage_result, threshold, criterion='distance')

# クラスタリング結果を比較
print(clustered)
print(iris.target)

出力結果

様子も見ずデータを絞ったりせず全てぶっこんで、というのはちょっと微妙なところですが setosa だけキレイに分かれましたね。setosa と、それ以外とで距離に開きがあるので、setosa だけ非常に特徴的ってことがわかります。それで versicolor と virginica はぼちぼち似ている、なんてところですね。

 

 

というわけで、階層型クラスタリングの話でした。

余談にはなりますが、そんなこんなで出来たやつをいい感じにクラスタ分けしたよ~^^って渡したら、分岐したやつを画像じゃなくて csv とかで欲しいって言われたのでそれはまた。

docker で MySQL を立ち上げたい。

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

タイトルそのまま。

// たぶん mysql 5.7 がプルされる
// が、わからんので DockerHub を要確認
$ docker pull mysql:latest

// mysqlコンテナの起動
//   オプションについて
//     -e    : 環境変数
//     --name: コンテナ名
//     -d    : バックグラウンド
//     -p    : ポートバインディング(ホスト:ゲスト)
//   環境変数について
//     MYSQL_DATABASE     : 作るデータべース
//     MYSQL_USER         : 作るユーザ
//     MYSQL_PASSWORD     : 作るユーザのパスワード
//     MYSQL_ROOT_PASSWORD: ルートパスワード
$ docker run --name my_mysql \
  -e MYSQL_DATABASE=homestead \
  -e MYSQL_USER=homestead \
  -e MYSQL_PASSWORD=secret \
  -e MYSQL_ROOT_PASSWORD=secret \
  -d \
  -p 13306:3306 \
  mysql

// 接続確認
//   -h : ホスト名
//   -P : ポート番号
//   -u : ユーザ名
//   -p : パスワードを入力するプロンプトを出す
$ mysql -h 127.0.0.1 -P 13306 -u root -p

記事書くときに Docker Hub を確認したら 5.7.19 が latest で指定されているっぽい。というか 8.0 系も指定できるのか、検証なんかにも便利そうだ。

library/mysql - Docker Hub

でもって docker で MySQL が立ち上がると、こうさ、ほら、ユニットテスト回すときなんかに、こうしてクリーンな DB を提供できてよさそうだなーって。
イメージとしてはこんな感じ。

{
    ...
    "scripts": {
        "test": "phpunit --verbose",
        "docker-test": [
            "composer docker-db-remove"
            "composer docker-db-create",
            "composer test",
            "composer docker-db-remove"
        ],
        "docker-db-create": "docker run --name test_mysql -e MYSQL_DATABASE=homestead -e MYSQL_USER=homestead -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=secret -d -p 13306:3306 mysql",
        "docker-db-remove": "docker rm --force test_mysql",
        "docker-db-connect": [
          "#  Copy this command!",
          "#  mysql --host=127.0.0.1 --port=13306 --user=homestead --password=secret homestead"
        ]
    },
    ...
}

composer docker-test とやったらコンテナが立ち上がり 127.0.0.1:13306 で待ち受けているので、そこを指定するように phpunit.xml だとか .env だとかで DB 接続用の値を設定すればよい。テストの後はコンテナがポイされるので、テスト用のDBはいつでもクリーンに。テストでコケても大丈夫なように、最初にコンテナをポイしたほうがいいのかな。

composer スクリプトで I/O を全部引き渡す方法がわからなかったので docker-db-connect タスクが非常にあやしい感じだけど、これくらいでもまあまあ運用していけるんじゃないかなーー

Laravel でマイグレーションなどの artisan コマンドを実行するときのエラーを詳し表示した

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

artisan コマンドでエラーが出た場合、そのまま実行していると例外クラスとメッセージが出る程度で、どこでエラーになっとんねん!がわかりません。

実は自分で作った artisan コマンド含めてすべての artisan コマンドは verbose なオプションをサポートしています。そのためオプションを指定するだけで、例外を詳しく見ることができます。

$ php artisan migrate

  [Illuminate\Database\QueryException]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists (SQL: alter table `my_tables` drop `created_at`, drop `updated_at`)

  [Doctrine\DBAL\Driver\PDOException]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists
  
  [PDOException]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists 


$ php artisan migrate -v
                                                                                       
  [Illuminate\Database\QueryException (42000)]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists (SQL: alter table `my_tables` drop `created_at`, drop `updated_at`)

Exception trace:
 () at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:647
 Illuminate\Database\Connection->runQueryCallback() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:607
 Illuminate\Database\Connection->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:450
 Illuminate\Database\Connection->statement() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php:86
 Illuminate\Database\Schema\Blueprint->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:239
 Illuminate\Database\Schema\Builder->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:148
 Illuminate\Database\Schema\Builder->table() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:221
 Illuminate\Support\Facades\Facade::__callStatic() at /var/www/myapp/database/migrations/2017_08_28_190905_modify_my_tables.php:21
 ModifyMstGraphTransitionTerms11->up() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:357
 Illuminate\Database\Migrations\Migrator->Illuminate\Database\Migrations\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:363
 Illuminate\Database\Migrations\Migrator->runMigration() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:177
 Illuminate\Database\Migrations\Migrator->runUp() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:146
 Illuminate\Database\Migrations\Migrator->runPending() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:95
 Illuminate\Database\Migrations\Migrator->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php:69
 Illuminate\Database\Console\Migrations\MigrateCommand->fire() at n/a:n/a
 call_user_func_array() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
 Illuminate\Container\BoundMethod::Illuminate\Container\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:87
 Illuminate\Container\BoundMethod::callBoundMethod() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:31
 Illuminate\Container\BoundMethod::call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/Container.php:539
 Illuminate\Container\Container->call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:182
 Illuminate\Console\Command->execute() at /var/www/myapp/vendor/symfony/console/Command/Command.php:264
 Symfony\Component\Console\Command\Command->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:167
 Illuminate\Console\Command->run() at /var/www/myapp/vendor/symfony/console/Application.php:874
 Symfony\Component\Console\Application->doRunCommand() at /var/www/myapp/vendor/symfony/console/Application.php:228
 Symfony\Component\Console\Application->doRun() at /var/www/myapp/vendor/symfony/console/Application.php:130
 Symfony\Component\Console\Application->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:122
 Illuminate\Foundation\Console\Kernel->handle() at /var/www/myapp/artisan:35


  [Doctrine\DBAL\Driver\PDOException (42000)]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists

Exception trace:
 () at /var/www/myapp/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:107
 Doctrine\DBAL\Driver\PDOStatement->execute() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:449
 Illuminate\Database\Connection->Illuminate\Database\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:640
 Illuminate\Database\Connection->runQueryCallback() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:607
 Illuminate\Database\Connection->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:450
 Illuminate\Database\Connection->statement() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php:86
 Illuminate\Database\Schema\Blueprint->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:239
 Illuminate\Database\Schema\Builder->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:148
 Illuminate\Database\Schema\Builder->table() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:221
 Illuminate\Support\Facades\Facade::__callStatic() at /var/www/myapp/database/migrations/2017_08_28_190905_modify_my_tables.php:21
 ModifyMstGraphTransitionTerms11->up() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:357
 Illuminate\Database\Migrations\Migrator->Illuminate\Database\Migrations\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:363
 Illuminate\Database\Migrations\Migrator->runMigration() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:177
 Illuminate\Database\Migrations\Migrator->runUp() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:146
 Illuminate\Database\Migrations\Migrator->runPending() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:95
 Illuminate\Database\Migrations\Migrator->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php:69
 Illuminate\Database\Console\Migrations\MigrateCommand->fire() at n/a:n/a
 call_user_func_array() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
 Illuminate\Container\BoundMethod::Illuminate\Container\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:87
 Illuminate\Container\BoundMethod::callBoundMethod() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:31
 Illuminate\Container\BoundMethod::call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/Container.php:539
 Illuminate\Container\Container->call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:182
 Illuminate\Console\Command->execute() at /var/www/myapp/vendor/symfony/console/Command/Command.php:264
 Symfony\Component\Console\Command\Command->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:167
 Illuminate\Console\Command->run() at /var/www/myapp/vendor/symfony/console/Application.php:874
 Symfony\Component\Console\Application->doRunCommand() at /var/www/myapp/vendor/symfony/console/Application.php:228
 Symfony\Component\Console\Application->doRun() at /var/www/myapp/vendor/symfony/console/Application.php:130
 Symfony\Component\Console\Application->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:122
 Illuminate\Foundation\Console\Kernel->handle() at /var/www/myapp/artisan:35


  [PDOException (42000)]
  SQLSTATE[42000]: Syntax error or access violation: 1091 Can't DROP 'created_at'; check that column/key exists

Exception trace:
 () at /var/www/myapp/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:105
 PDOStatement->execute() at /var/www/myapp/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:105
 Doctrine\DBAL\Driver\PDOStatement->execute() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:449
 Illuminate\Database\Connection->Illuminate\Database\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:640
 Illuminate\Database\Connection->runQueryCallback() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:607
 Illuminate\Database\Connection->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Connection.php:450
 Illuminate\Database\Connection->statement() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php:86
 Illuminate\Database\Schema\Blueprint->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:239
 Illuminate\Database\Schema\Builder->build() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php:148
 Illuminate\Database\Schema\Builder->table() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:221
 Illuminate\Support\Facades\Facade::__callStatic() at /var/www/myapp/database/migrations/2017_08_28_190905_modify_my_tables.php:21
 ModifyMstGraphTransitionTerms11->up() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:357
 Illuminate\Database\Migrations\Migrator->Illuminate\Database\Migrations\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:363
 Illuminate\Database\Migrations\Migrator->runMigration() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:177
 Illuminate\Database\Migrations\Migrator->runUp() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:146
 Illuminate\Database\Migrations\Migrator->runPending() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php:95
 Illuminate\Database\Migrations\Migrator->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php:69
 Illuminate\Database\Console\Migrations\MigrateCommand->fire() at n/a:n/a
 call_user_func_array() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29
 Illuminate\Container\BoundMethod::Illuminate\Container\{closure}() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:87
 Illuminate\Container\BoundMethod::callBoundMethod() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:31
 Illuminate\Container\BoundMethod::call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Container/Container.php:539
 Illuminate\Container\Container->call() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:182
 Illuminate\Console\Command->execute() at /var/www/myapp/vendor/symfony/console/Command/Command.php:264
 Symfony\Component\Console\Command\Command->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Console/Command.php:167
 Illuminate\Console\Command->run() at /var/www/myapp/vendor/symfony/console/Application.php:874
 Symfony\Component\Console\Application->doRunCommand() at /var/www/myapp/vendor/symfony/console/Application.php:228
 Symfony\Component\Console\Application->doRun() at /var/www/myapp/vendor/symfony/console/Application.php:130
 Symfony\Component\Console\Application->run() at /var/www/myapp/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:122
 Illuminate\Foundation\Console\Kernel->handle() at /var/www/myapp/artisan:35

ちなみにサポートするオプションと、表示レベルは以下のようになっています。


// /vendor/laravel/framework/src/Illuminate/Console/Command.php
// ※抜粋

use Symfony\Component\Console\Output\OutputInterface;

protected $verbosity = OutputInterface::VERBOSITY_NORMAL;

protected $verbosityMap = [
    'v' => OutputInterface::VERBOSITY_VERBOSE,
    'vv' => OutputInterface::VERBOSITY_VERY_VERBOSE,
    'vvv' => OutputInterface::VERBOSITY_DEBUG,
    'quiet' => OutputInterface::VERBOSITY_QUIET,
    'normal' => OutputInterface::VERBOSITY_NORMAL,
];
オプション レベル 生数値
v OutputInterface::VERBOSITY_VERBOSE 64
vv OutputInterface::VERBOSITY_VERY_VERBOSE 128
vvv OutputInterface::VERBOSITY_DEBUG 256
quiet OutputInterface::VERBOSITY_QUIET 16
normal OutputInterface::VERBOSITY_NORMAL 32

まあ、これってソースのとこにもあるんですけど、ベースになってる Symfony2 のものなんですけどね。

Verbosity Levels (current)

Symfony2 で定義されている値を使って artisan コマンドも同じように verbose な指定をサポートしているんですねー。もし自分で作るコマンドも verbose な指定をサポートするなら、指定されている定数を目安に出力すると良さそう。

Google Analytics API を使ってアシストコンバージョンを取得したかった

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

Google Analytics の API を使ってアシストコンバージョンをとりたいが、通常のセッション数などを取得できる Core Reporting API にはアシストコンバージョンという指標は無く取得することができない。 Multi-Channel Funnels Reporting API を使うことでその指標は取得できるようになる。がトラップがあり、やりたいことはできなかったので、タイトルが過去形。

なお MCF API のドキュメントにあるサンプルでは Java で書かれているが、言語別のライブラリが持っているインターフェースはおよそ同じなので、読み換えればよい。

Multi-Channel Funnels Reporting API - デベロッパー ガイド  |  アナリティクス Multi-Channel Funnels Reporting API  |  Google Developers

PHP ならこんな具合にして MCF をコールできる。

// セッション準備
session_start();

// API利用のための認証
//==================================================
$client = new \Google_Client();

// クライアントキー、シークレットの設定
// Developer Console で登録すると発行される
$client->setClientId('...');
$client->setClientSecret('...');

// オフラインアクセスを付与するとリフレッシュトークンを使って無期限にアクセスできる
$client->setAccessType('offline');

// 認証画面を都度表示する
$client->setApprovalPrompt('force');

// リダイレクトURLの設定
// Developer Console で設定した値と合わせないとエラーになる
$client->setRedirectUri('.....');

// アクセス権限の設定
$client->addScope([\Google_Service_Analytics::ANALYTICS_READONLY]);


// 認証が通ってくるとcodeがついてくるのでコレを利用して認証を通す
if (!empty($_GET['code'])) {
    unset($_SESSION['refresh_token']);
    $client->fetchAccessTokenWithAuthCode($_GET['code']);
    $_SESSION['refresh_token'] = $client->getRefreshToken();

} else {

    // リフレッシュトークンがなければ認証URLに飛ばす
    // とりあえず試すならセッションから読み込むと良い。
    if (empty($_SESSION['refresh_token'])) {
        header('Location: ' . $client->createAuthUrl());
        exit;
    }

    // リフレッシュトークンで認証する
    $client->refreshToken($_SESSION['refresh_token']);
}


// Google Analytics とのやり取りするオブジェクト準備
//==================================================
$analytics = new \Google_Service_Analytics($client);


// MCF API を実行する
//==================================================
$gaRequestResult = $analytics->data_mcf->get(
    // プロファイルID
    'ga:......',

    // 開始日
    '30daysAgo',
    // 終了日
    'yesterday',

    // 取得するメトリクス
    'mcf:assistedConversions',

    // 追加のオプション
    [
        'dimensions' => 'mcf:source',
        'filters' => 'mcf:source=@.',
    ]
);


// 結果確認
//==================================================
var_dump($gaRequestResult);

この例だと、流入元別のアシストコンバージョンを30日前から昨日まで範囲で取得する。また流入元の値に "." が含まれていることを条件とする。 流入元とは言うが (direct) のような値も入ってきて厄介。そこで「"." が含まれていること」にすればドメインが付いているものだけ抜けるという仕組み。

Google Developer Console で、このあたりから API 登録をすると動かせるようになる。

https://console.cloud.google.com/apis/dashboard

しかし、実はこの MCF API にはトラップがある。それはディメンションの設定にある。

Google Analytics の画面上からはデバイス別のアシストコンバージョンを確認することができるが API からはこれを確認するすべはない。ドキュメントを見ても MCF API のディメンションにはデバイスの話はない。

ディメンションと指標のリファレンス  |  アナリティクス Multi-Channel Funnels Reporting API  |  Google Developers

また MCF API を呼ぶときにディメンションに Core Reporting API の値を設定してもエラーになってしまう。逆に Core Reporting API を呼び出すときに MCF API のもの(アシストコンバージョンなど)を設定してもエラーになる。

ちなみに Core Reporting API で使える指標はここ。

Dimensions & Metrics Explorer  |  アナリティクス Reporting API v4  |  Google Developers

まあ。アシストコンバージョンそんな重要じゃねーからよくね、って話なのかもしれない。

Chrome の console.log で CSS プロパティが指定できるよ

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

以前からごみばこいんでは、コンソールにようこそメッセージを出していました。

Chrome Devtools のドキュメントを見ると console.log の詳細が書かれています。

診断とコンソールへのログ出力  |  Web  |  Google Developers

ドキュメントには CSS プロパティが利用できるとあるのですが、なぜかこれまでは色とサイズが変えられる程度だと思っていました。試すと text-shadow が出来たり border が出来たりと、様々なプロパティが効くようです。

var properties1 = [
    'font-size: 8em;',
    'color: white;',
    'padding: 5px;',
    'background: linear-gradient(red, orange, yellow, green, aqua, blue, violet);',
    'text-shadow: white 0 0 15px;'
];

var properties2 = [
    'font-size: 8em;',
    'color: #222;',
    'text-shadow: white 0 0 3px;',
    'margin-left: calc(-15em - 6px);',
];
console.log(
    '%cうぇるかむとぅーごみばこいん!%cうぇるかむとぅーごみばこいん!',
    properties1.join(''),
    properties2.join('')
);

いくつかプロパティを試してみたのですが display だったり float だったり position 、 top や left あたりは無効化されているようです。。

で。実は Inspector を使うと、ここも HTML/JS/CSS で構成されていることがわかります。
Inspector は URL バーに chrome://inspect/ と入力するとアクセス出来ます。その中でも Other の項目を見ると Developer Tools について Inspect することができます。

DevTools の DevTools ができるなんてちょっと不思議な光景ですねw

こうやって console.log した結果を眺めてみると %c で指定したものは span タグの style 属性に入っています。
これでは :beforeなどのセレクタが使えなかったり、階層構造が持てないのでアニメーションしたりなどなども出来ないです。先にあった使えないプロパティのところもあるので、凝ったことをするのはできなさそうですねー。残念。

MySQL の文字コードについてチョット調べた

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

MySQL 使ってて utf8_unicode_ci や utf8_general_ci とか mb4 …? とプチ事故が起きたので解消するべく調べた。

照合順序とは

「utf8_general_ci」といった文字コードの指定してるっぽいやつは、文字列カラムにおけるコレーション(=照合順序)と呼ばれる。ここで指定された値によって、MySQL が文字列を解釈したり比較を行うようになっている。

表記は 文字コード_文字セット名_比較方法(※特殊なものとして *_bin もある)

  • 文字コード
    • utf8mb4 とか utf8 とか cp932 とか
  • 言文字セット名
    • japanese とか german とか
    • Unicodeに限り、japanese はないので general か unicode か bin を選ぶ。
      • (言語によって文字の特殊性があるらしく、その言語別の定義があるが日本語はない)
  • 比較方法
    • 3種類あある
    • ci
      • 大文字小文字を比較しない
    • cs
      • 大文字小文字を比較する
    • bin
      • バイナリとして判断する
    • よく使う utf8... では ci と bin のみになる…?(何でー?)

どんな照合順序が使えるかを確認する

show collation というクエリを実行することで確認できる。

show collation;
Collation Charset Id Default Compiled Sortlen
big5_chinese_ci big5 1 Yes Yes 1
big5_bin big5 84 Yes 1
dec8_swedish_ci 8-Dec 3 Yes Yes 1
dec8_bin 8-Dec 69 Yes 1
cp850_general_ci cp850 4 Yes Yes 1
cp850_bin cp850 80 Yes 1
hp8_english_ci hp8 6 Yes Yes 1
hp8_bin hp8 72 Yes 1
koi8r_general_ci koi8r 7 Yes Yes 1
koi8r_bin koi8r 74 Yes 1
latin1_german1_ci latin1 5 Yes 1
latin1_swedish_ci latin1 8 Yes Yes 1
latin1_danish_ci latin1 15 Yes 1
...

※ like など SQL を書くときに使うような演算子を利用して絞り込みができたり、 ORDER BY 並び順を設定できる。

show collation where Collation like 'utf8%';
Collation Charset Id Default Compiled Sortlen
utf8_general_ci utf8 33 Yes Yes 1
utf8_bin utf8 83 Yes 1
utf8_unicode_ci utf8 192 Yes 8
utf8_icelandic_ci utf8 193 Yes 8

MySQL サーバーのバージョンによって使える照合順序が違うので、環境によって結果が変わることがある。(古いと utf8mb4 がないとかそういう) 5.6, 5.7 あたりならこの記事の話は問題ないはず。詳しい一覧はここで確認ができる。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 10.1.14 MySQL でサポートされる文字セットと照合順序

*_unicode_ci と *_general_ci の違い

照合順序の違いによってどんなことが起こるか、実際にクエリを発行しつつ確認する。

# バージョン確認
select version();
# 5.7.17


# アルファベットの大文字小文字、半角全角
#============================================================
SET NAMES 'utf8' COLLATE 'utf8_unicode_ci';
select 'A' = 'a', 'A' = 'A', 'A' = 'a';
# 1, 1, 1
 
SET NAMES 'utf8' COLLATE 'utf8_general_ci';
select 'A' = 'a', 'A' = 'A', 'A' = 'a';
# 1, 0, 0
 
SET NAMES 'utf8' COLLATE 'utf8_bin';
select 'A' = 'a', 'A' = 'A', 'A' = 'a';
# 0, 0, 0
 
 
# ひらがな, 半角カタカナ + 半濁音
#============================================================
SET NAMES 'utf8' COLLATE 'utf8_unicode_ci';
select 'は' = 'パ';
# 1
 
SET NAMES 'utf8' COLLATE 'utf8_general_ci';
select 'は' = 'パ';
# 0
 
SET NAMES 'utf8' COLLATE 'utf8_bin';
select 'は' = 'パ';
# 0

この結果をまとめると。

半角全角の比較 大文字小文字の比較 ひらがなカタカナの比較
unicode_ci できない できない できない
general_ci できる できない できる
bin できる できる できる

というわけで general_ci か bin を使うようにするとこのあたりの比較がちゃんと出来てよさそう。

utf8 と utf8mb4 の違い

MySQL の UTF-8 は 3 バイトの幅しかなく、絵文字などを表現するためには 4 バイトが必要。きっと歴史的な背景から utf8 と utf8mb4 が分裂している。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 10.1.10.7 utf8mb4 文字セット (4 バイトの UTF-8 Unicode エンコーディング)

先程と同じように文字コードを指定して比較していく。

SET NAMES 'utf8' COLLATE 'utf8_unicode_ci';
select 'あ' = '?', '?' = '?';
# 0, 1

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
select 'あ' = '?', '?' = '?';
# 0, 1

SET NAMES 'utf8' COLLATE 'utf8_general_ci';
select 'あ' = '?', '?' = '?';
# 0, 0

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';
select 'あ' = '?', '?' = '?';
# 0, 1

SET NAMES 'utf8' COLLATE 'utf8_bin';
select 'あ' = '?', '?' = '?';
# 0, 0

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_bin';
select 'あ' = '?', '?' = '?';
# 0, 0

SET NAMES 'utf8' COLLATE 'utf8_unicode_520_ci';
select 'あ' = '?', '?' = '?';
# 0, 1

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_520_ci';
select 'あ' = '?', '?' = '?';
# 0, 0

// あれ、このあたり文字化けしてるかも…? 
// あ=絵文字1, 絵文字1=絵文字2; と絵文字を比較してます。。

むむ、結構厄介。絵文字が入力される可能性があり、ちゃんと識別したい場合は utfmb4_bin を使うのがよさそう。

参考リンク

utf8_unicode_ci に対する日本の開発者の見解 - かみぽわーる
MySQL と寿司ビール問題 - かみぽわーる

Laravel5 で Log ファサードを使ったときに一緒に標準出力にも出した

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

artisan コマンドを作ったとき、何かと様子を出力するために Log ファサードを使ってログファイルに出している。

 

ちなみに。そのとき Monolog のハンドラを使って、レベル別にファイルを分けたりなどしている。こんな具合にするとログレベルごとにファイルが分かれる、めっちゃ便利。

// bootstrap/app.php
$app->configureMonologUsing(function ($monolog) {
    $logConfigure = config('log');
    foreach ($logConfigure['file'] as $logLevel => $logFile) {
        $path = storage_path('logs/' . $logFile . '.log');
        $handler = new \Monolog\Handler\RotatingFileHandler(
            $path,
            $logConfigure['rotate'],
            $logLevel,
            false,
            $logConfigure['permission']
        );
        $handler->setFormatter(new Monolog\Formatter\JsonFormatter());
        $monolog->pushHandler($handler);
    }
});


// config/log
return [
    'permission' => 0777,
    'rotate' => 30,
    'file' => [
        \Monolog\Logger::DEBUG     => 'debug',
        \Monolog\Logger::INFO      => 'debug',
        \Monolog\Logger::NOTICE    => 'debug',
        \Monolog\Logger::WARNING   => 'warning',
        \Monolog\Logger::ERROR     => 'error',
        \Monolog\Logger::CRITICAL  => 'error',
        \Monolog\Logger::ALERT     => 'error',
        \Monolog\Logger::EMERGENCY => 'error',
    ],
];

自分で用意する artisan コマンド、ようはバッチでは様子の出力などをファイルに出すために Log::info(); とかして、いろいろな情報をログに出ていく。この ID のデータをやるよー、とか、おわったよー、とか、色々。

しかし開発中はそれって厄介で、パッとコマンド実行してログ見て。いや、横で tail -f とかしておけば良いのだろうけど、それはそれでちょっと面倒だなあ。というか artisan コマンドって verbose なオプションをサポートしているので、それをうまく使ってログに出すと同時に標準出力にも出せないだろうか。

と思って出来たのがコレ。

namespace App\Console;

use Illuminate\Console\Command;
use Monolog\Handler\StreamHandler;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

trait LogWithStdout
{
    /**
     * execute メソッドに割り込み Monolog の設定をする
     */
    public function execute(InputInterface $input, OutputInterface $output)
    {
        // verboseなオプションが付いたらログを画面に出す
        if ($output->getVerbosity() >= $this->verbosityMap['v']) {
            $monolog = \Log::getMonolog();
            $handler = new StreamHandler("php://stdout", 'info');
            $monolog->pushHandler($handler);
        }

        // 元の処理の呼び出し
        return parent::execute($input, $output);
    }
}
namespace App\Console\Commands;

use App\Console\LogWithStdout;
use Illuminate\Console\Command;

class FetchTrafficSummary extends Command
{
    use LogWithStdout;

    public function handle()
    {
        Log::info('start!');

        ...

        Log::info('complete!');
    }
}

本当はサービスプロバイダにしてイベントにしてあげたほうがキレイなのだが、 Laravel の実装上、それは出来ないっぽい…。うぬ。。

具体的にはコマンドの発火時は取れるのだが verbose オプションがあるかどうかが取れない。 OutputInterface がイベントに入ってこないのだ。ソースも追いかけてみたがイベント引数に入ってくるのは Application インスタンスだけ。 OutputInterface を持ったものは Application から Command を呼ぶときにしか作られないらしい。

 
 
とまあ、作ってみたものの Artisan コマンドでも $this->info などして標準出力に出せるので、そちらをオーバーラップしたほうがキレイなんじゃないかなーと思った。こういうイメージ。

public function info($string, $verbosity = null)
{
    parent::info($string, $verbosity);
    \Log::info($string);
}

どちらかといえばこちらのほうが、メソッド別にログファサードのメソッドを呼べるのでパッと見でわかりやすそうだ。

TF-IDF ってのを使うと単語の重要度がわかるよって聞いたので調べた

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

TF-IDF は文章における、単語の重みづけを行うもの。
処理した文章中のうち、ある単語はどの程度重要なものか、出現度合いから重み付けを計算する。
プログラム、機械からは文章の分析っていってもよくわからないので、そのよくわからない特徴をわかるようにするための、数値化する方法の1つ。

前提として TF-IDF には複数の文章を入力する必要がある。

TF-IDF = TF * IDF

TF = 1つの文章において、ある単語の出現回数 / 文章内の単語数
→ 文章1つずつの単語の出現頻度がわかる。

IDF = log( 文章数 / ある単語が出現する文章数 ) + 1
→ 横断的に使われる単語は低い値になる。

簡単な文章を入れつつ実際の数字を計算してみる。

 
step1:文章の入力

文章
リンゴとレモンとレモン
リンゴとミカン

 
step2:単語に分割(ここでは形態素解析し、名詞だけに絞ったとする)

文章 分割した結果
リンゴとレモンとレモン リンゴ レモン レモン
リンゴとミカン リンゴ ミカン

 
step3:TF値、IDF値を計算する

文章 TF:リンゴ TF:レモン TF:ミカン
リンゴとレモンとレモン 1 / 3 = 0.33 2 / 3 = 0.67 0 / 3 = 0
リンゴとミカン 1 / 2 = 0.5 0 / 2 = 0 1 / 2 = 0.5
単語 IDF
リンゴ log(2 / 2) + 1 = 1
レモン log(2 / 1) + 1 = 1.3
ミカン log(2 / 1) + 1 = 1.3

 
step4:TF-IDFを計算する

文章 TF-IDF:リンゴ TF-IDF:レモン TF-IDF:ミカン
リンゴとレモンとレモン 0.33 * 1 = 0.33 0.67 * 1.3 = 0.87 0 * 1.3 = 0
リンゴとミカン 0.5 * 1 = 0.5 0 * 1.3 = 0 0.5 * 1.3 = 0.65

 

この結果からわかることとして。

  • 「リンゴ」は横断的に出現するので値が低くなる。
  • 「レモン」は文章1だけに多く使われるので値に特徴が強く出る。
  • 「ミカン」は文章2だけに使われているが、リンゴと同程度しか使われていないので、特徴が大きく出るわけではない。
  • この2つの文章を切り分けるには レモン の様子を見ればよいらしい。ということで、確かに単語の重要度がわかるよ、っていうのはあっているらしい。

ただ、全ての文章・全ての単語を利用するという計算ロジックの都合、文章を逐次増やして差分計算していくー、というのは出来なさそうなので、そういう用途はむりそうだなー。
例えばこの計算済み TF-IDF の結果に加えて「バナナとレモン」を追加したときに困っちゃうね、という話。この量なら良いけど、もっと文章量が増えてもっと単語量が増えると、毎回計算し直すコストがかかる。

Composer で指定したライブラリがインストールできないんだけど!!と言われたと

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

composer require コマンドでライブラリを入れようとすると「ライブラリをいれたいけど入れられないよ!」なんて怒られることがある。いや、 composer install でも起こることあるわ。

起きたこと

※既に他のライブラリを入っている状態

$ composer require --dev "phpdocumentor/phpdocumentor:^2.8"
You are running composer with xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug
./composer.json has been updated

Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.
 
  Problem 1
    - phpdocumentor/phpdocumentor v2.8.0 requires herrera-io/phar-update 1.0.3 -> satisfiable by herrera-io/phar-update[1.0.3].
    - phpdocumentor/phpdocumentor v2.8.1 requires herrera-io/phar-update 1.0.3 -> satisfiable by herrera-io/phar-update[1.0.3].
    - phpdocumentor/phpdocumentor v2.8.2 requires herrera-io/phar-update 1.0.3 -> satisfiable by herrera-io/phar-update[1.0.3].
    - phpdocumentor/phpdocumentor v2.8.3 requires herrera-io/phar-update 1.0.3 -> satisfiable by herrera-io/phar-update[1.0.3].
    - phpdocumentor/phpdocumentor v2.8.4 requires herrera-io/phar-update 1.0.3 -> satisfiable by herrera-io/phar-update[1.0.3].
    - phpdocumentor/phpdocumentor v2.8.5 requires herrera-io/phar-update 1.0.3 -> satisfiable by herrera-io/phar-update[1.0.3].
    - Conclusion: don't install herrera-io/phar-update 1.0.3
    - Installation request for phpdocumentor/phpdocumentor ^2.8 -> satisfiable by phpdocumentor/phpdocumentor[v2.8.0, v2.8.1, v2.8.2, v2.8.3, v2.8.4, v2.8.5].
 
 
Installation failed, reverting ./composer.json to its original content.

原因

Composer は依存ライブラリの解決を行う際、それぞれのライブラリの composer.json に記載されているバージョンを解釈し、お互いに問題のない、なるべく最新のものを選択しようとします。

このとき「あるライブラリではAの1.0.* を使います。別のあるライブラリではAの2.0.*を使います」といった記述がされていると、エラーになってしまうようです。

あるいは composer.lock としてバージョンが固定されてしまうときにも同様の問題が起こります。依存を再解決しようとするときに上記のようにぶつかってしまうことがあるようです。

対応策

順番に確認していくと良さそう。

  1. そもそも composer.json で書かれているバージョンとエラーになっているバージョンとが噛み合わない
    • → 使っているライブラリ、または入れようとしているライブラリのバージョンを見直す
  2. そんなことはない、依存していたんだ、というくらいのものでエラーが出てる
    • → composer.lock を消して composer install

どちらにしても、依存ライブラリのバージョンが変わるはずなので、テストの実行や動作確認を行ったほうがよいです。

1 2 3 4 5 6 7 8 9 10 11 12 13