$yuzu->log();

技術ネタなど。

ポケモンGo!のアンテナサイトを作った。技術仕様やアプリケーション仕様などなど

酔った勢いで、ポケモンGo!のアンテナサイトを作ってみました。

pokemongo-mtm.xyz

こういうのって、スピードが大事ですよね。検索ボリュームが多い時にリリースできたのでよかったです。 1時間ぐらいでさくっと。

使用した技術

サーバーはGMOクラウドのVPSのメモリ2GB、月額1,280円のプラン。

composer.json

 "require": {
        "cakephp/cakephp": "2.*",
        "cakephp/debug_kit": "*",
        "intervention/image": "dev-master",
        "intervention/imagecache" : "*",
        "cakedc/migrations": "*",
        "nanapi/cakephp-redis": "*"
    },

アプリケーションの仕様

  • hourlyで指定したRSSをクロール。
  • サムネイルの取得は対象サイトのog:image、なかったら、RSSのdescriptionに含まれている、imgタグから取得。
  • PVのカウントはRedisで貯めてって、デイリーバッチで、MariaDBに貯めこむ。

悲しかった事。

CakeDCのマイグレーションプラグインを使ったのですが、マイグレーションプラグインがStringクラスを使っていて、PHP7から「型と同じクラス名」を作れないという制約があるため、exception error が発生するという悲しみ。
なので手動でDB作りました。

今後やりたいこと

広告周りは適当にimobileのクリック広告しか貼ってないですが、ユーザの志向にあったものを採用していきたいです。

ポケモンGo!をやってみて。

電池の減り早い! モバイルバッテリー必須ですね。
軽くて大容量なおすすめモバイルバッテリー↓

FXシステムトレードフレームワーク Jiji をCentOS7にセットアップ

FXシステムトレードフレームワークJiji」のセットアップメモです。

「Jiji」のドキュメントにはHeroku、AWS、Dockerの3つの方法が載っており、前者2つは詳細に説明が書かれていますが、3つ目の「Dockerにインストール」はパッケージインストールが省略されています。

この記事はCentOS7での1からのセットアップ記事です。誰かの参考になれば。

Docker、Git、docker-composeのインストール

# システムをアップデート
$ sudo yum update -y

# Gitのインストール
$ sudo yum install -y git

# Dockerの最新版をインストール
$ curl -sSL https://get.docker.com/ | sh
$ docker -v
Docker version 1.10.2, build c3959b1
$ sudo systemctl start docker
$ sudo systemctl enable docker
$ sudo usermod -a -G docker YOURUSERNAME

# docker-composeをインストール
$ curl -L https://github.com/docker/compose/releases/download/1.6.0/docker-compose-`uname -s`-`uname -m` > /tmp/docker-compose
$ sudo mv /tmp/docker-compose /usr/local/bin/
$ sudo chmod +x /usr/local/bin/docker-compose

docker-compose設定ファイルのひな形をチェックアウト

$ git clone https://github.com/unageanu/docker-jiji2

オレオレ証明書の作成

# スーパーユーザにスイッチ
$ sudo su
# jiji用のディレクトリ作成、移動
$ mkdir -p /etc/ssl/certs/jiji
$ cd /etc/ssl/certs/jiji
# 秘密鍵を生成
$ openssl genrsa 2048 > server.key
# CSRを作成
$ openssl req -new -key server.key > server.csr
# サーバー証明書を作成
$ openssl x509 -sha256 -days 365 -req -signkey server.key < server.csr > server.crt
# アクセス権を制限
$ sudo chown root.root server.key
$ sudo chmod 600 server.key
# スーパーユーザから抜ける
$ exit

docker-compose.yml の編集

vim ~/docker-jiji2
jiji:
  container_name: jiji_jiji
  image: unageanu/jiji:latest
  environment:
    # サーバー内部で秘匿データの暗号化に使うキー
    # 必ず変更して使用してください。
    USER_SECRET: 20e43fa41e75c9fe958f5f11bc9e79d2174b50b
  links:
    - mongodb

mongodb:
  container_name: jiji_mongodb
  image: mongo:3.0.7
  ports:
    # MongoDBのポート番号
    # 必要に応じて変更してください。
    - "27018:27017"
  volumes:
    # MongoDBのデータを保存するディレクトリ
    # デフォルトでは、コンテナ内に作成します。(この場合、コンテナを再作成すると、データが初期化されます)
    # コメントアウトしてパスを設定することで、ホストマシンの任意のディレクトリに変更することができます。
    # './' で始めることで、docker-compose.ymlからの相対パスで指定可能です。
    # - ./path/to/data/dir:/data/db

nginx:
  container_name: jiji_nginx
  image: unageanu/jiji-nginx:latest
  links:
    - jiji
  ports:
    # Jijiのポート番号
    # 443に変更します
    - "443:443"
  volumes:
    # SSL証明書のパス
    # './path/to/server.crt' にサーバー証明書、
    # './path/to/server.key' に秘密鍵を指定します。
    # './' で始めることで、docker-compose.ymlからの相対パスで指定可能です。
    - /etc/ssl/certs/jiji/server.crt:/etc/nginx/cert/ssl.crt:ro
    - /etc/ssl/certs/jiji/server.key:/etc/nginx/cert/ssl.key:ro

Dockerコンテナ起動

$ sudo /usr/local/bin/docker-compose up -d
Creating jiji_mongodb
Creating jiji_jiji
Creating jiji_nginx

アクセス

https://<インストール先ホスト>:<docker-compose.ymlで設定したJijiのポート/デフォルトは8443>

【CentOS】迷惑メール(スパム)扱いされない為の最低限設定しておきたい3つの設定【Postfix】

さくらVPSCentOSの環境を構築し、localhost(ローカルホスト)からPostfixでメールを送信しても、なにも設定していないと高確率で迷惑メール(スパム)扱いされてしまいます。
Yahoo!メールは幾分か緩いですが、Gmailはほぼ100%迷惑メール(スパム)扱いされます。

最近は無料でSMTPサーバを貸してくれるサービスもあり、そちらを使う場合もあるかもしれませんが、localhost(ローカルホスト)から送信する場合もあると思うので、そちらで最低限しておきたい3つの設定を記します。

当方の環境は下記の通りです。

サーバー: さくらVPS(CentOS)
ドメイン: お名前.comで契約したhoge.com
メールソフト: Postfix

1. DNS逆引きレコード変更

さくらVPSの場合、契約したタイミングで、さくらのドメインが割り振られますが、WEBサービスを運営する場合は独自ドメインを取得し、そちらで運用してると思います。

何も設定せずにサーバーのIPを逆引きした場合、さくらのドメインが表示されるため、運用しているWEBサービスドメインと異なり、迷惑メール(スパム)扱いされます。

ですので、まずはDNS逆引きレコードを運営しているWEBサービスと同じドメインに変更しましょう。

さくらVPSの場合、コントロールパネルから逆引き設定できます。 f:id:yuzurus:20150613144542p:plain

確認は以下のコマンドを叩いて、ドメインが変わっていればOK

$ nslookup IPアドレス

2. SPFレコードの設定

Sender Policy Framework(センダー・ポリシー・フレームワーク)とは、電子メールにおける送信ドメイン認証のひとつ。差出人のメールアドレスが他のドメインになりすましていないかどうかを検出することができる。 SPF もしくは SPF認証 とも呼ばれる。

端的に言えば設定しておけば成りすましじゃないよ、ということを伝えることができるので、SPFレコードを設定してください。

お名前.comの場合は下記の画像のようにしてください。 f:id:yuzurus:20150613142310p:plain

ホスト名: 空
TYPE: TXT
TTL: 3600(お好みで)
VALUE: v=spf1 +a:hoge.com ~all

DKIMの設定

Domainkeys Identified Mail(DKIM)は、電子署名方式の送信ドメイン認証である。 DKIMでは送信側で電子メールに電子署名を付加し、受信側でその電子署名を照合するという方法で送信者のドメイン認証を行う。

DKIMの設定はOpenDKIMを使います。

sudo yum install opendkim

OpenDKIMを使って鍵を作成します。

sudo opendkim-genkey -D /etc/opendkim/keys/ -s hoge_com_selector -d hoge.com

作成された/etc/opendkim/keys/hoge_com_selector.txt の内容をDNSに登録してください。

f:id:yuzurus:20150613150429p:plain ホスト名: hoge_com_selector._domainkey
TYPE: TXT
TTL: 3600(お好みで)
VALUE: v=DKIM1; k=rsa; p=MIGfMA0GCSq…

/etc/opendkim.confの設定

# 送信と受信
Mode sv

# comment out
# KeyFile /etc/opendkim/keys/default.private

# uncomment
KeyTable refile:/etc/opendkim/KeyTable

# uncomment
SigningTable refile:/etc/opendkim/SigningTable

# uncomment
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts

# uncomment
InternalHosts refile:/etc/opendkim/TrustedHosts 

/etc/opendkim/KeyTableの設定

hoge_com_selector._domainkey.hoge.com hoge.com:hoge_com_selector:/etc/opendkim/keys/hoge_com_selector.private

/etc/opendkim/SigningTableの設定

*@hoge.com hoge_com_selector._domainkey.hoge.com

Postfixの設定

/etc/postfix/main.cfの末尾に下記を追記

smtpd_milters = inet:127.0.0.1:8891

OpenDKIMとPostfixのrestart

sudo service opendkim restart
sudo service postfix restart

OpenDKIMの設定が正しいか確認

メールを送信して署名が追加されているか確認する。

# mail example@gmail.com
Subject: Test
Test
.

メールログ(/var/log/maillog)を確認し、

DKIM-Signature header added (s=hoge_com_selector, d=hoge.com)

という文字があれば設定完了。

参考 http://nobnoob.hatenablog.com/entry/2013/04/01/093120

【チュートリアル】最新フレームワークCakePHP3でブックマーカーを作ってみる その2

yuzurus.hatenablog.jp

前回の続きになります。

ログイン機能の実装

ログイン機能はCakePHP2同様AuthComponentを利用します。

AppContorollerに追加しましょう

// In src/Controller/AppController.php
namespace App\Controller;
 
use Cake\Controller\Controller;
 
class AppController extends Controller
{
    public function initialize()
    {
        $this->loadComponent('Flash');
        $this->loadComponent('Auth', [
            'authenticate' => [
                'Form' => [
                    'fields' => [
                        'username' => 'email',
                        'password' => 'password'
                    ]
                ]
            ],
            'loginAction' => [
                'controller' => 'Users',
                'action' => 'login'
            ]
        ]);
        
        // Allow the display action so our pages controller
        // continues to work.
        $this->Auth->allow(['display']);
    }
}

ここではflashとauthコンポーネントをロードしています。

usernameにemail,passwordにpasswordを使用します。このへんはCakePHP2と同様ですね。

現在ログインしていないので、どのページにアクセスしても/users/loginにリダイレクトされます。

では/users/loginページを実装しましょう。

// In src/Controller/UsersController.php

public function login()
{
    if ($this->request->is('post')) {
        $user = $this->Auth->identify();
        if ($user) {
            $this->Auth->setUser($user);
            return $this->redirect($this->Auth->redirectUrl());
        }
        $this->Flash->error('Your username or password is incorrect.');
    }
}
// src/Template/Users/login.ctp
<h1>Login</h1>
<?= $this->Form->create() ?>
<?= $this->Form->input('email') ?>
<?= $this->Form->input('password') ?>
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>

これでログイン機能ができました。 f:id:yuzurus:20150609230203p:plain

ログアウト機能の実装

UsersController.phpにlogoutメソッドを追加します。

public function logout()
{
    $this->Flash->success('You are now logged out.');
    return $this->redirect($this->Auth->logout());
}

ここもCakePHP2と同様ですね。

ユーザ登録の有効化

現在ユーザ登録しようとしてもログイン画面へリダイレクトされるので、それをさせないようにします。

// In src/Controller/UsersController.php
public function beforeFilter(\Cake\Event\Event $event)
{
    $this->Auth->allow(['add']);
}

addメソッドが認証を必要としないことを示します。

ブックマークへのアクセス制限

現在ログインしさえすればすべてのユーザのブックマークにアクセスできてしまします。

ですので、その人が登録したブックマークのみアクセス出来るようにしましょう。

authorizationアダプターを使用します。

// In src/Controller/AppController.php
public function initialize()
{
    $this->loadComponent('Flash');
    $this->loadComponent('Auth', [
        'authorize'=> 'Controller',//added this line
        'authenticate' => [
            'Form' => [
                'fields' => [
                    'username' => 'email',
                    'password' => 'password'
                ]
            ]
        ],
        'loginAction' => [
            'controller' => 'Users',
            'action' => 'login'
        ],
        'unauthorizedRedirect' => $this->referer()
    ]);
 
    // Allow the display action so our pages controller
    // continues to work.
    $this->Auth->allow(['display']);
}

// add
public function isAuthorized($user)
{
    return false;
}

基本はアクセス禁止で、ホワイトリスト方式が良いでしょう。

// In src/Controller/BookmarksController.php
public function isAuthorized($user)
{
    $action = $this->request->params['action'];
 
    // The add and index actions are always allowed.
    if (in_array($action, ['index', 'add', 'tags'])) {
        return true;
    }
    // All other actions require an id.
    if (empty($this->request->params['pass'][0])) {
        return false;
    }
 
    // Check that the bookmark belongs to the current user.
    $id = $this->request->params['pass'][0];
    $bookmark = $this->Bookmarks->get($id);
    if ($bookmark->user_id == $user['id']) {
        return true;
    }
    return parent::isAuthorized($user);
}

これで表示、編集、削除しても元のページに戻ります。 エラーページも用意しましょう

// In src/Template/Layout/default.ctp
// Under the existing flash message.
<?= $this->Flash->render('auth') ?>

一覧ページとフォームの修正

追加機能と一覧ページには以下の問題があります。 1. ブックマークを追加や編集する場合、ユーザを選択出来てしまう 2. 一覧ページでは他のユーザのブックマークまで閲覧できてしまう

これらを修正していきます。

フォームの修正

Bookmarks/add.ctpから input('user_id') を削除してユーザIDを編集できないようにしましょう。

またBookmarksController.phpをaddメソッドとeditメソッドを以下のように修正しましょう。

public function add()
{
    $bookmark = $this->Bookmarks->newEntity();
    if ($this->request->is('post')) {
        $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data);
        $bookmark->user_id = $this->Auth->user('id');
        if ($this->Bookmarks->save($bookmark)) {
            $this->Flash->success('The bookmark has been saved.');
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error('The bookmark could not be saved. Please, try again.');
    }
    $tags = $this->Bookmarks->Tags->find('list');
    $this->set(compact('bookmark', 'tags'));
}
public function edit($id = null)
{
    $bookmark = $this->Bookmarks->get($id, [
        'contain' => ['Tags']
    ]);
    if ($this->request->is(['patch', 'post', 'put'])) {
        $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data);
        $bookmark->user_id = $this->Auth->user('id');
        if ($this->Bookmarks->save($bookmark)) {
            $this->Flash->success('The bookmark has been saved.');
            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error('The bookmark could not be saved. Please, try again.');
    }
    $tags = $this->Bookmarks->Tags->find('list');
    $this->set(compact('bookmark', 'tags'));
}

一覧画面の修正

続いて一覧画面でログインしているユーザーのブックマークだけを表示するようにします。 pagenate()に条件を加えて以下の様に修正してください。

public function index()
{
    $this->paginate = [
        'conditions' => [
            'Bookmarks.user_id' => $this->Auth->user('id'),
        ]
    ];
    $this->set('bookmarks', $this->paginate($this->Bookmarks));
}

タグ機能の修正

現状ではTagsControllerがすべてのアクセスを拒否するため、新しいタグの登録ができません。 アクセスを許可する代わりに、区切りのテキストで入力するUIを開発しましょう。 これにより良いユーザユーザーエクスペリエンスを手に入れられるだけでなく、ORMのさらなる機能を使ってみることができます。

処理済フィールドの追加

エンティティへのアクセスをシンプルな方法にするために、エンティティに仮想的で処理済なフィールドを追加しましょう。src/Model/Entity/Bookmark.phpに以下を追加してください。

use Cake\Collection\Collection;

protected function _getTagString()
{
    if (isset($this->_properties['tag_string'])) {
        return $this->_properties['tag_string'];
    }
    if (empty($this->tags)) {
        return '';
    }
    $tags = new Collection($this->tags);
    $str = $tags->reduce(function ($string, $tag) {
        return $string . $tag->title . ', ';
    }, '');
    return trim($str, ', ');
}

これは私たちが$bookmark->tag_stringとすれば処理済のプロパティにアクセスすることを可能にします。 後で、このプロパティをフォームのinputで使います。 また保存に必要なのでtag_stringプロパティをエンティティの_accessibleリストに追加してください。

    protected $_accessible = [
        'user_id' => true,
        'title' => true,
        'description' => true,
        'url' => true,
        'user' => true,
        'tags' => true,
        'tag_string' => true,
    ];

ビューの更新

エンティティが更新されたので、新しいinput要素を追加することができます。add、editの、既存のtags._idsinputを以下のように変更してください。

<?= $this->Form->input('tag_string', ['type' => 'text']) ?>

タグ文字列の永続化

既存のタグを文字列として表示できるようになりました。同様にタグ文字列を保存できるようにしてみましょう。

tag_stringを$_accessibleに追加したので、ORMはこのデータをリクエストからエンティティにコピーします。

beforeSave()フックメソッドを使用して、エンティティに対してタグ文字列のパースとタグリストの検索/追加ができます。以下のコードをBookmarksTable.phpに追加してください。

public function beforeSave($event, $entity, $options)
{
    if ($entity->tag_string) {
        $entity->tags = $this->_buildTags($entity->tag_string);
    }
}

protected function _buildTags($tagString)
{
    $new = array_unique(array_map('trim', explode(',', $tagString)));
    $out = [];
    $query = $this->Tags->find()
        ->where(['Tags.title IN' => $new]);

    // Remove existing tags from the list of new tags.
    foreach ($query->extract('title') as $existing) {
        $index = array_search($existing, $new);
        if ($index !== false) {
            unset($new[$index]);
        }
    }
    // Add existing tags.
    foreach ($query as $tag) {
        $out[] = $tag;
    }
    // Add new tags.
    foreach ($new as $tag) {
        $out[] = $this->Tags->newEntity(['title' => $tag]);
    }
    return $out;
}

まとめ

ブックマークアプリケーションにおいて、簡単なログイン認証、アクセスコントロールなどを実装できました。 これを機会にCakePHP3に触れる人が増えてくればなによりです。

【チュートリアル】最新フレームワークCakePHP3でブックマーカーを作ってみる その1

そろそろちゃんと最新フレームワークCakePHP3を触らないといけない気がしてきたので、チュートリアルとしてCakePHP3を使ってブックマーカーを作ってみます。

環境はMacOSXVirtualbox+VagrantCentOS乗っけてます。

インストール

PHPのインストール

PHPをインストールしていない場合はまずPHPから

PHP5.6系をインストールします。

リポジトリはremiで。

% rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
% yum install --enablerepo=remi --enablerepo=remi-php56 php php-intl php-mbstring php-mysqlnd
$ php -v
PHP 5.6.9 (cli) (built: May 15 2015 09:40:22)
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2015 Zend Technologies
    with Xdebug v2.3.2, Copyright (c) 2002-2015, by Derick Rethans

Composerのインストール

よもやぺちぱーでComposerを使っていない人はいないと思いますが、一応インストール方法を。

グローバルにインストールしちゃいます。

$ curl -sS https://getcomposer.org/installer | php
% mv composer.phar /usr/local/bin/composer

Mysqlのインストール

とりあえず5.1以上をインストールしてください。

インストール方法は面倒なので飛ばします。

今回は5.6.24を使います。

CakePHP3のインストール

さていよいよCakePHP3のインストールです。

ワンライナーでインストールできてしまいます。良い時代ですね。

今回はプロジェクト名をbookmarkerで。

composer create-project --prefer-dist cakephp/app bookmarker
Installing cakephp/app (3.0.3)
  - Installing cakephp/app (3.0.3)
    Downloading: 100%

Created project in bookmarker
Loading composer repositories with package information
Installing dependencies (including require-dev)
省略
  - Installing cakephp/cakephp (3.0.6)
    Loading from cache
  - Installing cakephp/migrations (1.1.1)
    Downloading: 100%
  - Installing cakephp/debug_kit (3.1.5)
    Loading from cache
  - Installing cakephp/bake (1.0.9)
    Downloading: 100%
省略
Created `config/app.php` file
Set Folder Permissions ? (Default to Y) [Y,n]? Y
省略
Updated Security.salt value in config/app.php

MigrationsがCakeDCじゃなくなったのですね。

パーミッション云々聞かれるのでYとしましょう。

Saltも勝手にランダム値に設定してくれるみたいです。

2系でもインストールは楽だったのですが更に楽になりましたね。

ディレクトリ構成を見てみましょう。

$ tree -L 2
.
|-- README.md
|-- bin
|   |-- cake
|   |-- cake.bat
|   `-- cake.php
|-- composer.json
|-- composer.lock
|-- config
|   |-- app.default.php
|   |-- app.php
|   |-- bootstrap.php
|   |-- bootstrap_cli.php
|   |-- paths.php
|   |-- routes.php
|   `-- schema
|-- index.php
|-- logs
|   |-- cli-error.log
|   `-- empty
|-- phpunit.xml.dist
|-- plugins
|   `-- empty
|-- src
|   |-- Console
|   |-- Controller
|   |-- Model
|   |-- Shell
|   |-- Template
|   `-- View
|-- tests
|   |-- Fixture
|   |-- TestCase
|   `-- bootstrap.php
|-- tmp
|   |-- Bake-Controller-controller-ctp.php
|   |-- Bake-Element-Controller-add-ctp.php
|   |-- Bake-Element-Controller-delete-ctp.php
|   |-- Bake-Element-Controller-edit-ctp.php
|   |-- Bake-Element-Controller-index-ctp.php
|   |-- Bake-Element-Controller-view-ctp.php
|   |-- Bake-Element-form-ctp.php
|   |-- Bake-Layout-default-ctp.php
|   |-- Bake-Model-entity-ctp.php
|   |-- Bake-Model-table-ctp.php
|   |-- Bake-tests-fixture-ctp.php
|   |-- Bake-tests-test-case-ctp.php
|   |-- add-ctp.php
|   |-- cache
|   |-- debug_kit.sqlite
|   |-- edit-ctp.php
|   |-- index-ctp.php
|   |-- sessions
|   |-- tests
|   `-- view-ctp.php
|-- vendor
|   |-- aura
|   |-- autoload.php
|   |-- bin
|   |-- cakephp
|   |-- cakephp-plugins.php
|   |-- composer
|   |-- dnoegel
|   |-- empty
|   |-- ircmaxell
|   |-- jakub-onderka
|   |-- mobiledetect
|   |-- nesbot
|   |-- nikic
|   |-- psr
|   |-- psy
|   |-- robmorgan
|   `-- symfony
`-- webroot
    |-- css
    |-- favicon.ico
    |-- img
    |-- index.php
    `-- js

38 directories, 40 files

srcディレクトリにMVCが入るようになったのですね。

起動してみる

ビルドインサーバがあるので試してみます。

ローカルホスト上で動いている場合は

$ bin/cake server
Welcome to CakePHP v3.0.6 Console
---------------------------------------------------------------
App : src
Path: /vagrant/blog/bookmarker/src/
DocumentRoot: /vagrant/blog/bookmarker/webroot
---------------------------------------------------------------
built-in server is running in http://localhost:8765/
You can exit with `CTRL-C`

http://localhost:8765/ でアクセスできます。

仮想マシン上で動いている場合はホストを指定しなければなりません。

bin/cake server -H 0.0.0.0

Welcome to CakePHP v3.0.6 Console
---------------------------------------------------------------
App : src
Path: /vagrant/blog/bookmarker/src/
DocumentRoot: /vagrant/blog/bookmarker/webroot
---------------------------------------------------------------
built-in server is running in http://0.0.0.0:8765/
You can exit with `CTRL-C`

vagrantでIPを192.168.33.50と指定しているので

http://192.168.33.50:8765/ でアクセスできます。

f:id:yuzurus:20150609113143p:plain

起動できました。

データベース(MySQL)の設定

CakePHPからMySQLへアクセスできるように設定をします。

Config/app.phpを自分の環境に合わせて設定してください。

    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            /**
             * CakePHP will use the default DB port based on the driver selected
             * MySQL on MAMP uses port 8889, MAMP users will want to uncomment
             * the following line and set the port accordingly
             */
            //'port' => 'nonstandard_port_number',
            'username' => 'root',
            'password' => '',
            'database' => 'cake_bookmarks',
            'encoding' => 'utf8',
            'timezone' => 'Asia/Tokyo',
            'cacheMetadata' => true,

            /**
             * Set identifier quoting to true if you are using reserved words or
             * special characters in your table or column names. Enabling this
             * setting will result in queries built using the Query Builder having
             * identifiers quoted when creating SQL. It should be noted that this
             * decreases performance because each query needs to be traversed and
             * manipulated before being executed.
             */
            'quoteIdentifiers' => false,

            /**
             * During development, if using MySQL < 5.6, uncommenting the
             * following line could boost the speed at which schema metadata is
             * fetched from the database. It can also be set directly with the
             * mysql configuration directive 'innodb_stats_on_metadata = 0'
             * which is the recommended value in production environments
             */
            //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
        ],

timezoneをAsia/Tokyoにした場合以下のようなエラーが出るかもしれません。

Exception: SQLSTATE[HY000]: General error: 1298 Unknown or incorrect time zone: 'Asia/Tokyo' in [/vagrant/bookmarker/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php, line 94]
php-intl

MySQLタイムゾーンの設定に不備があるので下記のコマンドを実行してください

$ mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql -p

bakeを使ってコードを生成する

CakePHPチュートリアルでお馴染みのbakeでコードを生成していきます。

$ bin/cake bake all users
$ bin/cake bake all bookmarks
$ bin/cake bake all tags

コントローラ、モデル、ビュー、テストケースが生成されます。

テスト大事ですね!

では http://192.168.33.50:8765/bookmarks にアクセスしてみましょう。 f:id:yuzurus:20150609165505p:plain

ユーザを登録したりブックマークを登録したりタグを登録してみましょう。

パスワードのハッシュ化

f:id:yuzurus:20150609223405p:plain

ユーザ登録するとパスワードがプレーンテキストで保存されています。

例えばこの状態でサービスリリースをしてSQLインジェクションなどでユーザ情報が流出すると、このIDとパスワードを使って他のサービスのログインを試み、2次被害してしまいます。そういうことを防ぐためにパスワードは必ず暗号化して保存しなければなりません。

CakePHP2ではModelはModelクラスひとつでしたが、CakePHP3ではTableとEntityに分かれています。 TableはDBのテーブルにアクセスするクラスで、Entityは単一のレコードを扱うクラスです。

パスワードのハッシュ化ロジックはUserのEntityクラスのpasswordセッターに追加します。

_set{カラム名}とすると、カラムの保存前にメソッドが呼び出されるようです。

<?php
namespace App\Model\Entity;
use Cake\Auth\DefaultPasswordHasher;
use Cake\ORM\Entity;

/**
 * User Entity.
 */
class User extends Entity
{

    /**
     * Fields that can be mass assigned using newEntity() or patchEntity().
     *
     * @var array
     */
    protected $_accessible = [
        'email' => true,
        'password' => true,
        'bookmarks' => true,
    ];


    // Code from bake.

    protected function _setPassword($value)
    {
        $hasher = new DefaultPasswordHasher();
        return $hasher->hash($value);
    }

}

DefaultPasswordHasherはBCrypt(Blowfish暗号)になります。

これで、パスワードはハッシュ化されて保存されるようになります。

CakePHP2系ではSecurityコンポーネントで指定できるソルトは$2a$のみでしたが、DefaultPasswordHasherではデフォルトで$2y$なのですね。

特定タグのブックマークを取得する

http://192.168.33.50:8765/bookmarks/tagged/funny/cat/gifs でアクセスした時にfunny, cat, gifsのタグが付いたブックマークをを表示する、という機能をつけてみます。

routeの実装

config/routes.phpに以下を追加

Router::scope(
    '/bookmarks',
    ['controller' => 'Bookmarks'],
    function ($routes) {
        $routes->connect('/tagged/*', ['action' => 'tags']);
    }
);

CakePHP2を経験してる人であれば意味はわかりますね。

では対応するメソッドを実装します。

tagsメソッドの実装

public function tags()
{
    $tags = $this->request->params['pass'];
    $bookmarks = $this->Bookmarks->find('tagged', [
        'tags' => $tags
    ]);
    $this->set(compact('bookmarks', 'tags'));
}

次に

$this->Bookmarks->find('tagged', [
        'tags' => $tags
]);

に当たる部分を実装します。

finder メソッドの実装

BookmarksTable.phpに以下を実装します。

public function findTagged(Query $query, array $options)
{
    $fields = [
        'Bookmarks.id',
        'Bookmarks.title',
        'Bookmarks.url',
    ];
    return $this->find()
        ->distinct($fields)
        ->matching('Tags', function ($q) use ($options) {
            return $q->where(['Tags.title IN' => $options['tags']]);
        });
}

これがカスタムファインダーメソッドです。これはCakePHP3の目玉機能の一つでクエリを再利用可能にします。

Viewの実装

ロジックができたので次はそれを表示するViewを実装します。

Bookmarks/tags.ctpに実装します。

<h1>
    Bookmarks tagged with
    <?= $this->Text->toList($tags) ?>
</h1>

<section>
<?php foreach ($bookmarks as $bookmark): ?>
    <article>
        <h4><?= $this->Html->link($bookmark->title, $bookmark->url) ?></h4>
        <small><?= h($bookmark->url) ?></small>
        <?= $this->Text->autoParagraph($bookmark->description) ?>
    </article>
<?php endforeach; ?>
</section>

これで表示できるようになります。 「探偵物」というタグに「金田一少年の事件簿」というブックマークを紐付けたので以下のような表示になります。

http://192.168.33.50:8765/bookmarks/tagged/探偵物 f:id:yuzurus:20150609222758p:plain

とりあえず今日はここまで。 次回はログイン、ログアウト、アクセス制限などの機能を追加していきます。

【チュートリアル】最新フレームワークCakePHP3でブックマーカーを作ってみる その2

参考 http://book.cakephp.org/3.0/en/tutorials-and-examples/bookmarks/intro.html

CakePHPで学ぶ継続的インテグレーション (impress top gear)

CakePHPで学ぶ継続的インテグレーション (impress top gear)

マルコフ連鎖の実験のためにMeCabをCentOSにインストール

マルコフ連鎖の実験をしたいがために、 京都大学情報学研究科と日本電信電話株式会社コミュニケーション科学基礎研究所が開発しているオープンソース形態素解析エンジン「MeCab」 (和布蕪 めかぶ)をインストールするためのメモです。

本体をインストール

$ cd /tmp
$ wget https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7cENtOXlicTFaRUE
$ tar zxfv mecab-X.X.tar.gz
$ cd mecab-X.X
$ ./configure --enable-utf8-only
$ make
$ make check
# make install

辞書ファイルをインストール

$ cd /tmp
$ wget https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7MWVlSDBCSXZMTXM
$ tar zxfv mecab-ipadic-2.7.0-XXXX.tar.gz
$ cd mecab-ipadic-2.7.0-XXXX
$ ./configure --with-charset=utf8
$ make
# make install

試してみる

$ mecab
すもももももももものうち
すもも   名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも  名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも  名詞,一般,*,*,*,*,もも,モモ,モモ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち  名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS

うまくいってるっぽい。 このライブラリを使って今後マルコフ連鎖の実験をしていく。

入門 自然言語処理

入門 自然言語処理

Apacheのmod_expiresというキャッシュモジュールを使ってWEBサイトを高速化しよう

Apacheにはmod_expiresというキャッシュモジュールがあります。 クライアント側で画像やCSSなどファイルをキャッシュさせるモジュールです。 これを利用し、転送量を減少させてWEBサイトの負荷対策します。 キャッシュを利用すると負荷対策ばかりか、表示速度も向上します。

mod_expiresが組み込まれているか確認

通常の方法でApacheをインストールしていた場合、mod_expiresはデフォルトで組み込まれています。 CentOSの場合/etc/httpd/conf/httpd.confに以下の行があることを確認してください。

LoadModule expires_module modules/mod_expires.so

mod_expiresの設定方法

httpd.confの377行目付近に以下を追加してください。

<IfModule mod_expires.c>
ExpiresActive on
ExpiresByType image/png "access plus 12 hours"
ExpiresByType image/jpeg "access plus 12 hours"
ExpiresByType image/gif "access plus 12 hours"
ExpiresByType text/css "access plus 12 hours"
ExpiresByType text/javascript "access plus 12 hours"
</IfModule>

これはpng,jpg,gif,css,javascriptを12時間という期限でキャッシュさせるという宣言です。 Apacheを再起動されば反映されます。

mod_expiresの設定が反映されているか確認

Chromeのdev toolで画像のレスポンスヘッダを確認してみましょう。

f:id:yuzurus:20150208015804p:plain

max-age=43200なので43200秒、つまり12時間にちゃんと設定されていることが確認できました。

mod_expiresの設定するにあたっての注意

もしjavascriptなどに変更があり、更新した場合、クライアント側にキャッシュが残っており表示が意図しなくなる場合があります。
その場合はhoge-v1.jsなどファイル名でバージョン管理するか、hoge.js?123456などパラメータにタイムスタンプなどを付与すれば解決します。(モダンなWEBアプリケーションフレームワークにはこのような機能が含まれていると思います。)

ハイパフォーマンスWebサイト ―高速サイトを実現する14のルール

ハイパフォーマンスWebサイト ―高速サイトを実現する14のルール