PhpStormでGruntを使う

最近はフロントエンド部分の開発を行っていなかったので、JavaScriptやCSSの結合や圧縮をすることがなかったのですが、サイトの運用開始後にちょっとだけ修正するといったことも増えてきたので、この作業を自動化してみることにしました。

今回使ったのはGruntとというJavaScriptで記述するタスクランナーです。

https://www.jetbrains.com/help/phpstorm/grunt.html

基本的な手順は、PhpStorm 2017.2 Helpに書かれています。

今回はGit Bashを使って設定したので、GitとNode.js(windows版)を予めインストールしておきます。

管理者権限で起動したGit Bachでgrunt-cliをインストール
$ npm install -g grunt-cli

この時に表示されるインストールパスをメモ。

[Run] - [Edit Configurations...]でインタプリターの設定と、grant-cliのパスを指定。


package.jsonの初期化とgruntのインストール。必ず、プロジェクトディレクトリのルートに移動してからコマンドを実行します。
$ cd /d/workspace/hoge
$ npm init
$ npm install grunt --save-dev


[File] - [Settings...] - [Language & Frameworks] - [Node.js and NPM]

ここで、npm installが出来るっぽいんですが、上手くいかなかったので(インストールしたはずのパッケージがNot Foundになる)、引き続きGit Bashでインストール。
$ npm install grunt-contrib-concat --save-dev
$ npm install grunt-contrib-ugify --save-dev

d:¥workspace¥hoge¥gruntfile.js
module.exports = function (grunt) {
grunt.initConfig({
concat: {
files: {
src : ['assets/js/plugin.js','assets/js/script.js'],
dest: 'assets/js/concat.js'
}
},

uglify: {
dist: {
files: {
'assets/js/all.js': 'assets/js/concat.js'
}
}
}
});

grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('default', ['concat', 'uglify']);
};
plugin.jsとscript.jsを結合して、concat.jsとして保存して、それをminifyしてall.jsとして出力する例。

[View] - [Tool Window] - [Grunt]でGruntのツールウィンドウが開くので、そこから実行。

Craft CMS 覚え書き

Craft CMSというかYii Framework 2.6に関するメモ。

models


DBに保存されないデータモデル。
主に入力の検証を行う(ログインフォーム等のデータモデル)。
必須(required)、型(AttributeType)、規定値(default)、サイズ(length)などの属性を定義。


records


DBに保存するデータモデルは、Active Record(AR)として定義する。
DB型(ColumnType)、重複不可(unique)、主キー、外部キー、などRDBに関する定義は一通りできる。
Number型のPKは自動的にAuto Incrementになる。
作成したプラグインをインストールすると、recordsの定義通り、DBにテーブルが自動作成される。

リレーションの書き方サンプル

[MyPlugin_TagGroupRecord.php](親テーブル)
protected function defineAttributes()
{
return array(
'group_id' => array(AttributeType::Number, 'column' => ColumnType::PK, 'required' => true),
'tag_id' => array(AttributeType::Number, 'required' => true),
'content_id' => array(AttributeType::Number, 'required' => true),
);
}

public function defineRelations() {
return array(
'taggroup' => array(self::HAS_MANY, 'MyPlugin_TagRecord', 'tag_id'),
);
}


[MyPlugin_TagRecord.php](子テーブル)
protected function defineAttributes()
{
return array(
'tag_id' => array(AttributeType::Number, 'column' => ColumnType::PK, 'required' => true),
'name' => AttributeType::String,
);
}

public function defineRelations() {
return array(
'taggroup' => array(static::BELONGS_TO, 'MyPlugin_TagGroupRecord', 'tag_id', 'required' => true, 'onDelete' => static::CASCADE),
);
}

Craft CMSのdefineRelations()は、リレーションで親テーブルの主キー以外のカラムを参照しようとしても、主キーとリレーションを作成してしまうようなので、主キー以外とのリレーション定義は下記のようにするしかないかも。

[MyPlugin_TagRecord.php](子テーブル)
public function createTable() {
parent::createTable();
craft()->db->createCommand()->addForeignKey($this->getTableName(), 'tag_id', 'myplugin_taggroup', 'tag_id');
}



※入力>確認画面>保存のようなUIフローを持つ場合は、models/recordsを両方定義してもいい。

AWS LambdaでEC2インスタンスを自動起動・自動停止する

前回作成したZend Server 8 + ubuntuのインスタンスをLambdaで平日だけ稼働するようにスケジュールしてみました。

こちらの記事がわかりやすかったです。

LambdaとCloudWatch EventsでEC2の自動起動&自動停止をやってみた(Python版)
http://dev.classmethod.jp/cloud/aws/lambda_cloudwatch-events_ec2-star-stop-python/

最初、少し古い記事を読んでしまって、AWS CLIライブラリを含めたデプロイzipを作ってみたりしましたが、2017/04/11現在はAWS SDKがLambdaの実行環境に含まれていて、インスタンスの起動と停止だけなら、トリガーの設定とコードを書くだけでした。

Lambda 実行環境と利用できるライブラリ
http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/current-supported-versions.html

参考にした記事ではトリガーをCloudWatchのダッシュボードから作成していますが、Lambdaからもcronの設定ができるので簡単。

トリガーにCloudWatchイベント・スケジュールを設定し

ルール名lambda-instance-start
スケジュール式cron(0 0 ? * MON-FRI *)
トリガーの有効化ON
平日の朝9時に起動

これでCloudWatch側にルールが作成されます。

停止関数のほうは、こんな感じ。
ルール名lambda-instance-stop
スケジュール式cron(0 14 ? * MON-FRI *)
トリガーの有効化ON
平日の夜11時に停止


Zend Server Developer Edition (Ubuntu)

ローカルPCにインストールしていたZend Serverのライセンスが切れたのでAWSでZend Serverのインスタンスを借りることにしました。

AWS marketplaceで「PHP 5.6 - Zend Server Developer Edition (Ubuntu)」のインスタンスを契約(RHEL版もあります)。リージョンはap-northeast-1(Tokyo)、インスタンスタイプはt2.smallです。

Zend Server0.03/時$21.60/月
EC20.032/時$23.04/月
合計0.062/時$44.64/月
費用は1ヶ月稼働させ続けたら、月5,000円弱。
最初の30日はZend Serverの分は無料で、EC2のインスタンス料金だけで済みます。

これだけだと起動する度に毎回IPが変わってしまい、WordPressが使えなくなるので、Elastic IPで固定IPを割り当ててDNSに登録することにしました。
Zend Server + EC20.768/12時間
Elastic IP$0.06/12時間
合計$0.774/日
平日に12時間稼働させたとすると大体月額2000円弱になります(Elastic IPは割り当てたインスタンスが停止していた時間分が課金対象)。これなら$240のパッケージ(1年ライセンス)や月額$20のサブスクリプションより、EC2のインスタンスが使える分お得じゃないかなと。


とりあえず、MySQLが無かったのでインストール
$ sudo apt-get install mysql-server

phpMyAdminとWordPressはZend ServerのApplication Deployからインストール
WordPressは4.3.1だったので、4.7.3にアップデート

アップデートの際に、パーミッションエラーになったので

$ sudo chown -R www-data:zend /usr/local/zend/var/apps/http/(virtual-host)/80/4.3.1_11
$ sudo vi /usr/local/zend/var/apps/http/(virtual-host)/80/4.3.1_11/wp-config.php

wp-config.php
define('FS_METHOD','direct');

を追加

と、一通り設定が終わったところで、いつも使っているのがCentOSやAmazon LinuxなのでRHEL版にすればよかったと思ったり…。


Z-Rayを有効にしても、そこそこのレスポンスなので開発用には十分。もう少し使ってみてから、AWS Lambdaでインスタンスの起動と停止を自動化してみます。

MySQL Workbench 6.3.5でMalformed packetエラー

MySQL Workbench 6.3.5ですが、Windows版でSSH tunnel経由の接続を行うと"Malformed packet"エラーとなってしまい、接続できないようです。

http://bugs.mysql.com/bug.php?id=78947

6.3.4が入手できなかったので、6.1をインストールしなおしたら、問題無く接続できました。

"Malformed packet"のキーワードでググってしまうと、ネットワークアダプタ関連のWorkaroundばかりで、ハマってしまうのでメモ。

ウィッシュリストの合計金額を出す

Haswell-Eで自作PCを組もうと思っているのですが、価格変動が激しいので、Amazonのウィッシュリストを作成して、パーツを放り込んでチェックしてます。

ただ、個別の値下がりは表示されるのですが、総額で比較ができないのでこちらの記事を参考にブックマークレットでチェックすることにしました。

Amazonのほしい物リストの合計金額を出すブックマークレット
http://d.hatena.ne.jp/muranoki3/20091109/1257755109

HTMLの構造が変わっていてそのままでは動かないので、以下のように修正してます。

javascript:var total=0,r=document.getElementsByClassName("a-size-base a-color-price a-text-bold");for(var i=0;i<r.length;i++){total+=parseInt(r[i].innerHTML.replace(",","").match(/(¥d)+/)[0]);}alert("計"+r.length+"点で¥"+total);void(0);

Zend Studio 10.5でSmartyPDTを使う

Zend Studio 10.5を使っていて、古いコードをメンテナンスするときに、Smartyのテンプレートも識別してほしいので、SmartyPDTをインストール。
http://code.google.com/p/smartypdt/

しかし、インストール時に、以下のリポジトリを指定してインストールするとエラーが発生してインストールできません。
http://smartypdt.googlecode.com/svn/trunk/org.eclipse.php.smarty.updatesite/

以下のリポジトリを予め追加しておくと、依存ファイルを取得できるようになってインストールできます。
http://download.eclipse.org/releases/juno/

WordPressのquery_postsでmeta_valueに空文字列を使いたい

query_postsで

"meta_key=custom_field_name&meta_value=&meta_compare=!="

のような「カスタムフィールドの値が空の記事を除外」した記事を抽出したくても、生成されるWHERE文が

(wp_postmeta.meta_key = 'custom_field_name')

といった感じで、meta_value部分の条件が出力されないんですね。

ということで、posts_whereフィルタを使って回避するコードを書いてみました。

[functions.php]

add_filter('posts_where', 'my_posts_where'), 10, 2);
function my_posts_where($where, $query) {
if (strpos($where, "_wp_empty") !== false) {
$where = str_replace('_wp_empty', '', $where);
}
return $where;
}


これで、

"meta_key=custom_field_name&meta_value=_wp_empty&meta_compare=!="

のように条件を設定すればOK。

WordPressのBlog Optionをまとめて取得する

WordPressをマルチサイトで動かしていると、wp_optionsテーブルがブログ単位で作成されるので、まとめて設定内容を確認したいときに、めっさ面倒くさい。なので、まとめて設定値を取得できるストアドプロシージャを書いてみました。

call sp_get_blog_options('dbname', 'option_name', 0);

引数は
1)データベース名
2)取得したいoption_name値
3)group byするかどうかフラグ(0=しない,1=する)
の3つです。

ブログで使われているブログテンプレートを集計したい場合は

call sp_get_blog_options('wordpress', 'template', 1);

とすれば、各テンプレートの利用数を取得できます。最後の引数を0にするとblog_idとoptions_valueをそのままダンプします。

PHPで呼び出す場合は$wpdb->get_results()やmysql_query()ではエラーになるので注意してください。呼び出す場合はmysqli_query()で。あと、information_schemaにアクセスできるDBアカウントで実行するのもお忘れ無く。


DELIMITER $$

DROP PROCEDURE IF EXISTS `sp_get_blog_options` $$
CREATE PROCEDURE `sp_get_blog_options`(
IN db_name varchar(100),
IN option_value varchar(100),
IN summary_flag int
)
BEGIN
DECLARE not_found int DEFAULT 0;
DECLARE _blog_id bigint(20);
DECLARE _table_name varchar(100);
DECLARE _get_table_name varchar(100);
DECLARE cur CURSOR FOR SELECT blog_id FROM wp_blogs WHERE spam <> 1;
DECLARE cur2 CURSOR FOR SELECT table_name FROM `information_schema`.`tables` WHERE `table_name` = _table_name AND `table_schema` = db_name;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET not_found = 1;

DROP TABLE IF EXISTS `tmp_options`;
CREATE TEMPORARY TABLE `tmp_options` (`blog_id` bigint(20), `option_value` varchar(50));

OPEN cur;
loop1: LOOP
FETCH cur INTO _blog_id;
IF not_found THEN
CLOSE cur;
LEAVE loop1;
END IF;

OPEN cur2;
SET _table_name = CONCAT('wp_', _blog_id, '_options');
FETCH cur2 INTO _get_table_name;

IF not_found THEN
SET not_found = 0;
ELSE
SET @s = CONCAT('SELECT option_value INTO @option_value FROM `wp_', _blog_id, '_options` WHERE option_name=?');
PREPARE stmt FROM @s;
SET @val = option_value;
EXECUTE stmt USING @val;
DEALLOCATE PREPARE stmt;
INSERT INTO tmp_options VALUES (_blog_id, @option_value);
END IF;
CLOSE cur2;
END LOOP;
IF summary_flag THEN
SELECT tmp_options.option_value, count(tmp_options.option_value) AS counter FROM tmp_options GROUP BY tmp_options.option_value ORDER BY count(tmp_options.option_value);
ELSE
SELECT * FROM tmp_options;
END IF;
END $$

DELIMITER ;

WordPressのdbDelta()

WordPressのdbDelta()関数で、ハマったのでメモ。

dbDelta()関数は、CREATE TABLE文などを実行する際に、既に存在するテーブルかをチェックして、存在すればALTER TABLE文に変換してくれる便利なもの。

ですが、マルチサイトを運営していて、抱えているブログ数が増えてくると、このdbDelta()関数内で"SHOW TABLES"クエリーを投げる箇所がネックになってきます。DB内の全てのテーブルを列挙するので、SHOW TABLEの結果が数千〜数万になってくるとメモリ不足となり、エラーも吐かずにプロセスがスタックします。

どのタイミングで落ちるかというとブログ作成のタイミング。wpmu_create_blog()内のdbDelta()が呼ばれたところで落ちてしまいます。アカウントの新規登録、ブログの新規作成という結構、困る場所です。

BuddyPressを利用しているので、ブログの新規作成はBuddyPressテンプレート(/wp-content/themes/theme_name/blogs/create.php)でini_set()を実行して解決。

ちょっと困ったのが、アカウントの新規登録のタイミング。どこかのフィルタかアクションをフックして

@ini_set('memory_limit', '128M');


というような感じでメモリ上限を上げたいと考えたのですが、あまり良い場所が無いんですね。仕方なく、random_passwordフィルタをフックして、メモリ上限を上げる方法を取りました。

と、いっても対処療法なので、この問題で困る前にDBを分散したほうがいいんですけどね…。