CentOS7にXAMPPとCakePHP3とNode.js&Socket.ioをセットアップするまでの流れ
CakePHP上にチャットを構築しようとするとき用の備忘録
Socket.ioが通信方法をwebsocketに変更しようとすると、
変更を知らせるパケットのステータスコードが101ではなく
200になってしまうという問題(ポーリングは可能なため一応通信は可能だが、
socket.ioの意味がない)が起こり、その原因が分かりません。
そのため、今回はsocket.io.jsの読み込みはリダイレクトしますが、
そのjsがconnectする先は直接Node.jsにします。スマートじゃないですが
オーバーヘッドは少ないかもしれません。
参考にさせていただいたもの
得られた知見
- ApacheとPHPにはログファイルがある。
- php -mやphpinfo();で読み込まれているモジュールを確認できる。
今回はここでredisとigbinaryが読み込まれていることを確認した。 - RedisとNode.jsにはデバッグモードがある。
- elephant.ioはクライアントとして動作する(その為node.jsでフィルタ可)
php-emitterはサーバから送信(emit)する(その為node.jsではフィルタ出来ない、イベントもない)
今回はphp-emitterを使用し、こちらからのみ配信する仕様だったので、socket.ioにはほぼ何も書いていません。 - class not foundに悩まされているときは以下を試す。
チーム開発で、それぞれ実行環境が違う時に存在しないクラスを使用しない為にも使える。 - //ロード済みのextension一覧を表示する
debug(get_loaded_extensions()); - //include / require済みのファイル一覧を表示する、
今回これでcomposerのファイルが読み込めてないことに気づいた
debug(get_included_files()); - //宣言済みのクラス一覧を表示する
debug(get_declared_classes()); - yumでインストールしたパッケージがどこにインストールされたか確認する
rpm -ql パッケージ名 - yumでインストール可能なorインストールした(リポジトリ名の前に@がつく)
パッケージ一覧を表示する
yum list (お好みでgrep) - yumでインストールしたパッケージ一覧を表示する
yum list installed - runやshをダブルクリックで実行するには実行権限を付加することが必要。
chmod以外には右クリックのオプションから変更可能。
全部書いているので適度に読み飛ばしてください。
- CentOS7をインストールする。今回はGUIにしました。
- XAMPP for Linuxをダウンロードしてインストールする。
もし、PHPのバージョンを7.1にした場合は unable to initialize module Module compiled with module API=20151012 PHP compiled with module API=20160303と出る。この場合動作するPHPのバージョンは7.0。 - もしSELinuxが有効になっていたらApacheが起動しない可能性があるので無効にする←場合に依るんでしょうか?CUIの時は遭遇したがGUIでは遭遇しなかった。
- ファイアウォールの80と3000(Node.js用、3000じゃなくても良い)を開ける
--permanentを忘れずに - まずゲストOS側でApache、PHPMyAdminが動いているか確認
- ホストOSからphpMyAdminにログインしようとすると新しいXAMPPのセキュリティコンセプトなどとエラーが出るので、/opt/lampp/etc/extra/httpd-xampp.confのrequire localをコメントアウトし、適当なアドレスを許可する。今回はとりあえず動かしたかったので、require all grantedとした。
- ゲストOSがNATの場合はポートフォワーディングを設定する
- ホストOS側でApache、PHPMyAdminが動いているか確認
- phpMyAdminにCakePHP用のユーザを作る。ホスト名は%だとなぜか動かなかったのでlocalhostとした。
- CakePHPのconfig/app.phpのDB設定を編集する。
- MariaDBに論理バックアップしておいたSQLをぶちこむ
- phpのPATHを通すために~/.bashに以下を追記。
export PATH=$PATH:/opt/lampp/bin/php←場合による。 - composerそのものをインストールする
- composer installする。
- CakePHPプロジェクトを閲覧すると実行権限がないなどと怒られるので、
chmod -R 777 /opt/lampp/htdocs/{プロジェクト名}/logs
chmod -R 777 /opt/lampp/htdocs/{プロジェクト名}/tmp
のように権限を変更する。777はとりあえず動かしたかったので。 - この時点でCakePHPとMariaDBとの間に有効なデータベース接続があることを確認する
-
【ここからNode.js&Socket.io】
localhost:3000/nodejs/socket.ioの通信をApacheからNode.jsに移譲する(リバースプロキシ)ために
Apacheのhttpd.confをいじる。今回は、socket.io.jsの読み込みのみApacheを経由させ、
socket.io自体の通信は直接Nodeに渡します。なのでLoadModule ws-tunnelなどは記述せず、ProxyRequests Off
ProxyPass /nodejs http://localhost:3000 retry=0 timeout=600 keepalive=On
ProxyPassReverse /nodejs http://localhost:3000
とした。
余談ですが、socket.ioが起動した時にsocket.io.jsは自動で生成されます。 - node js,npmをインストールする
node.jsは最新ではなくLTS版の6.11.0を使用しました。 - redisをインストールする
makeとは、Makefileファイルに記述されているコマンドを実行し、コンパイルするコマンド
make installとは、コンパイルした結果を適切な場所にコピーしてコマンドラインから使えるようにしたりするコマンド
socket.io-php-emitter で PHP から emit する方法 - Qiita
wget http://download.redis.io/releases/redis-3.2.3.tar.gz
tar xzf redis-3.2.3.tar.gz
cd redis-3.2.3
make
make install
utils/install_server.sh - package.jsonがsocket.io,redisなど適切に書かれていることを確認してnpm installする(うまいこと行かなかったらnpm install -gする)
- use socket.io;を書く
- Redisクラスを使えるようにするphp-redisをインストールする
- extension=なんたらかんたら/redis.soとphp.iniに書くと、Unable to load dynamic library '/usr/lib64/php/modules/redis.so' - /usr/lib64/php/modules/redis.so: undefined symbol: igbinary_serialize in Unknown on line 0とエラーが出るのでyum install php70-php-igbinaryする
- なんやかんやする。以下実際に書いたコード
今回のVIrtualBoxの設定は、NAT接続でポートフォワーディングを23000→3000, 27780→80としています。
Apacheは80, Node.jsは3000, Redisは6379 です。
Node.js
ただCakePHPの一部に表示したかっただけなので、expressは入れていません。
上に書いた通り、PHP-emitterはサーバとして動作するのでconnectionイベントには接続されたことを通知するemitのみしています。
var app = require('http').createServer(handler); var io = require('socket.io')(app); app.listen(3000); var redis = require('socket.io-redis'); var adapter = io.adapter(redis({ host: '127.0.0.1', port: 6379 })); function handler (req, res) { res.writeHead(200); res.end(); } io.on('connection', function (socket) { socket.emit("messageFromPHP",""); });
CakePHP3 Controller
最初の方でPHP7.1を使用していたため、useが使用できずrequire_onceでphpファイルを一つ一つ読み込んでいましたが、PHP7.0に替えたところ特に読み込まずとも認識しました。6379はredisの標準ポートです。
TomController.php
$redis = new \Redis; $redis->connect('127.0.0.1', '6379'); $emitter = new \SocketIO\Emitter($redis); $emitter->emit('messageFromPHP', $num);
composer.json
上と同じく、PHP7.1を使用していたためにautoloadにemitterを入れたりしましたが、PHP7.0に入れ替えたところ、結果的にrequireを書き換えるのみでした。
"ashiina/socket.io-emitter": "0.8.0"
package.json
ただCakePHPの一部に表示したかっただけなので、expressは入れていません。
nodemonはコードが変更されたとき即時に変更を反映するものです。
{
"name": "sample-server",
"main": "off.js",
"dependencies": {
"socket.io": "^1.4.8",
"socket.io-redis": "^1.0.0"
},
"devDependencies": {
"nodemon": "^1.9.1"
}
}
CakePHP3 Template
Apache経由でNode.jsからsocket.io.jsを読み込んだあと、直接Node.jsにconnectしています。Apache経由で読み込む利点は、ApacheとNode.jsが異なるオリジン(ポート番号が違うため)であるので、Node.js側にクロスオリジンの設定(res.header("Access-Control-Allow-Origin", "*"など)がいらないことです。
ここのlocalhostは必要に応じて$_SERVER['SERVER_ADDR']とでもしてください。
bobby.ctp
<script src="/nodejs/socket.io/socket.io.js"></script> <script type="text/javascript"> var socket = io(' ); socket.on('messageFromPHP', function (data) { console.log(data); $('#activity-text').text(data); }); </script>
httpd.conf
websocketの通信を媒介する場合はws-tunnelのモジュールをロードなどするらしいです。私は出来ませんでした。
retry=0はリダイレクト先(Node.js)に接続出来ない状況でその情報をキャッシュさせないものです。つまり0ならば接続のたびにNode.jsに接続できることを確認します。実用的には3秒程度なんでしょうか?
ProxyRequests Off ProxyPass /nodejsretry=0 timeout=600 keepalive=On ProxyPassReverse /nodejs
追記
きちんとReadme.mdを読んだらRedisクラスはsocket.io-php-emitterにあった。確かにcomposer.jsonにrequire ptrofimov/tinyredisclientとある。しかし高機能なものを使うならPHP-redisをインストールしろということらしい。
CentOS7のGUIでXAMPPをインストールする
XAMPPの公式サイトから.runをダウンロードし、
実行権限を付加した後、sudo ./xamp-*.runとCentOS上で実行する。
./は必要。これがあることで、システムは./~.runがコマンドではなくファイルパスであることを認識できる。
Apacheのリバースプロキシに失敗する
AH00016: Configuration Failed
[Thu Jul 06 22:46:50.368024 2017] [core:notice] [pid 11224] SELinux policy enabled; httpd running as context system_u:system_r:httpd_t:s0
[Thu Jul 06 22:46:50.368903 2017] [suexec:notice] [pid 11224] AH01232: suEXEC mechanism enabled (wrapper: /usr/sbin/suexec)
[Thu Jul 06 22:46:50.395794 2017] [so:warn] [pid 11224] AH01574: module proxy_module is already loaded, skipping
[Thu Jul 06 22:46:50.395819 2017] [so:warn] [pid 11224] AH01574: module proxy_http_module is already loaded, skipping
[Thu Jul 06 22:46:50.395828 2017] [so:warn] [pid 11224] AH01574: module proxy_wstunnel_module is already loaded, skipping
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using localhost.localdomain. Set the 'ServerName' directive globally to suppress this message
[Thu Jul 06 22:46:50.398053 2017] [auth_digest:notice] [pid 11224] AH01757: generating secret for digest authentication ...
[Thu Jul 06 22:46:50.398625 2017] [lbmethod_heartbeat:notice] [pid 11224] AH02282: No slotmem from mod_heartmonitor
[Thu Jul 06 22:46:50.423201 2017] [mpm_prefork:notice] [pid 11224] AH00163: Apache/2.4.6 (CentOS) OpenSSL/1.0.1e-fips mod_fcgid/2.3.9 PHP/5.4.16 mod_wsgi/3.4 Python/2.7.5 configured -- resuming normal operations
[Thu Jul 06 22:46:50.423234 2017] [core:notice] [pid 11224] AH00094: Command line: '/usr/sbin/httpd -D FOREGROUND'
[Thu Jul 06 22:46:59.841797 2017] [proxy:error] [pid 11226] (13)Permission denied: AH00957: HTTP: attempt to connect to 127.0.0.1:3000 (localhost) failed
SELinuxのせいでした。
CakePHP3でトランザクションの使い方
計算処理を減らすため、中間テーブルと同時に行を挿入しなければならない時など、
トランザクション処理を行いたい場面はよくあります。
そんな時CakePHP3では
ConnectionManager::get('default')->transactional
というメソッドを使います。
使用例はこちらです。
$result = false;
try {
$result = $connection->transactional(function ( $connection ) use ( $insertOneQuery, $insertTwoQuery ) {
$insertOneQuery->execute();
$insertTwoQuery->execute();
return true;
});
}catch (\PDOException $e) {
}
transactional()の引数の無名関数内に実行したい処理を書きます。
もし外部の変数を使いたいときはuseで追加してください。
無名関数の返り値はtransactional()の返り値になります。
今回はトランザクションが成功したかどうかが必要だったので、無名関数の最後にreturn trueしました。
つまり成功した場合は$resultにtrue,失敗した場合は$resultにfalseが入ります。
PHPStormにコード補完させる in CakePHP3のテンプレート
拡張子ctpで表されるテンプレートファイルはHTMLとPHPが混在するファイルで非常に便利です。
しかし、PHPStormには、テンプレートファイルが何のクラスのインスタンスなのか理解できません。コントローラから$this->set(compact('hoge'))のようにセットされた変数も同様です。
つまりコード補完も効きません。
コード補完を効かせるためには、テンプレートがどのインスタンスなのか等をPHPDocに書いてやる必要があります。
PHPDocで変数の型を補足するのは、 @var クラス 変数名 の書式です。
これをテンプレートファイルの先頭に書きます。
<?php
/**
*
* ①対象:テンプレートファイルすべて
* テンプレートはすべてこのインスタンスとなる
* @var \App\View\AppView $this
*
* ②対象:コントローラから変数をセットしているテンプレートすべて
* 変数が存在することを宣言、そして、その型を指定する
* 例1 シンプルな例
* @var String $season
* 例2 配列の場合
* @var \App\Model\Entity\User[][] $users
*
*/
?>
参考はここです。
エンティティのデータを加工して取り出したいとき
仮想プロパティーの生成
アクセサーを定義することによって、現在存在しないフィールド・プロパティーへのアクセスを提供できます。 例えば、users テーブルが first_name
と last_name
列を持っていたとして、 フルネームのためのメソッドを作れるということです。
namespace App\Model\Entity;
use Cake\ORM\Entity;
class User extends Entity
{
protected function _getFullName()
{
return $this->_properties['first_name'] . ' ' .
$this->_properties['last_name'];
}
}
仮想プロパティーは、エンティティーに存在するかのようにアクセスできます。 プロパティー名は小文字と ”_” を使ってメソッド名を表記します。