Rubyでワンライナー

まずはPerlのプログラムから

先日、Railsを開発している若者と次のような会話があった。

若者:geditで正規表現を使って「^」を「,」で置換しようとしているのですが(数百行の文字列の行頭にコンマを挿入したいらしい)、うまくいかないんです。Hidemaruだとうまくいったはずなんですが。
私:たぶん、正規表現のHidemaruの独自拡張でできるかもしれないけど、普通はそれじゃ無理だよ。Rubyのワンライナーで置換しちゃえば。

そのことをふと今日思い出して、そもそもワンライナーという言葉とか知っているのかな、今時の若者は、とか思ってしまいました。

と同時に、そういう自分も以前のPerlが全盛の頃だったらワンライナーをよく使っていましたが、今ではほとんど記憶の墓場です(トホホホ)。なので復習がてら、まずはPerlのワンライナーを(思い出しながら)書いてみました。最初はワンライナーではちょっと見づらかったりするので、最初は複数行のスクリプトから。

正規表現的にはPerlでは下のような書き方で、$_という変数内の文字列を置換します($_は後述)。

s/^(.*)$/,\1/"

なので、次のような感じになるでしょうか。

while(<>){
    s/^(.*)$/,\1/;
    print;
}

簡単に説明します。

  1. while(<>)はコマンドラインのスクリプトファイルの後のファイル名をオープンして、文末まで1行ずつ処理する、Perl独自の書き方です。
  2. 1のループで、1行の内容は$_に入っています。
  3. $_ =~ s/^(.*)$/,\1/;」で(Rubyで言うところの破壊的メソッド)置換ができますが、「$_ =~」を省略すると暗黙的に$_が対象になるので、書いてありません。
  4. printで引数を省略すると、$_が出力します。

コマンドラインで、仮にこのスクリプトがtest.plにあり、またtmp.txtに対象の文字列があり、置換後の文字列をtmpp.converted.txtに書き出すとすれば、次のようになるでしょう。

perl test.pl tmp.txt > tmpp.converted.txt

もちろん、これで十分かもしれませんが、Perlにはワンライナーを書くのに便利なオプションがあって、もっと簡潔に書けます。

Perlのコマンドオプション(ワンライナー関係)

  1. -eオプション
    -eオプションに与えられた文字列をスクリプトとして実行します。

    perl -e 'script'
    
  2. -nオプション
    -nオプションをつけると以下のループの内部にスクリプトがあるような動作をします。

     while(<>) {
       script;
       .....
     }
    

    次のように実行します。

    perl -ne 'script' filename
    
  3. -pオプション
    -pオプションをつけると以下のループの内部にスクリプトがあるような動作をします。

     while(<>) {
       script;
       ....
     }continue{
       print $_;
     }
    

    次のように実行します。

    perl -pe 'script' filename
    

    ちなみに、continueブロックはfor文の「for(A;B;C;)」のCにあたり、1回ループが回ったときに実行されるブロックです。

  4. -lオプション
    -lオプションは、n,pオプションと組み合わせて使い、ループの前で、$_をchomp(改行コードの削除)を実施します。

     while(<>) {
       chomp $_;
       script;
       ....
       print $_;
     }
    

    次のように実行します。

     perl -ple 'script' filename
    
  5. -aオプション
    -aオプションは入力された行をsplit(分割)して、@Fという配列にセットします。分割パターンは/\s+/です。

    perl -ane 'print $F[0],"\n"' filename
    
  6. -Fオプション
    -Fオプションは-aオプションで分割する分割パターンを指定します。

    perl -F'/\t/' -ane 'print $F[0],"\n"' filename
    
  7. -iオプション
    これは対象のファイルを破壊的に変えてしまって、元のファイルはバックアップとして保存します。
    -iオプションの後に文字列を指定して、その文字列が拡張子として加えられてバックアップファイルができます。

    perl -i".bak" -pe 's/したがって/従って/g' tmp.txt
    

    tmp.txtファイルに含まれる”したがって”という文字列を”従って”に置換え、オリジナルのファイルをtmp.txt.bakという名前にしてバックアップしてくれます。

オプションにはまだまだありますが、Perl のワンライナーについての個人的なメモあたりを参照ください。

さてさてこれらのオプションの中の「ipe」を使うと

perl -i".bak" -pe "s/^(.*)$/,\1/" tmpp.txt

これで、tmpp.txtの全ての行頭に「,」が挿入され、オリジナルのファイルはtmpp.txt.bakで保存されます。

Rubyだってワンライナーが当然できるだろう

そこで、Rubyでも当然ワンライナーを書けるだろうと思って調べたら、当然ありました。

というか、上記に出ているPerlのオプションはRubyでも同じだし「$_」も同じようなので、私には、覚えなおす必要もなくラッキーです!とすると、こんな感じでできちゃいます。
注意すべきは、ダブルクォーテーションの中に「\」があるので、これをエスケープしているところでしょうか。

それからPerlではs/A/B/の「s」が置換の意味になり、「$_ =~ s/A/B/;」の「=~」で正規表現のマッチングをさせますが、Rubyの場合はsubメソッドを使って置換することと、デフォルトが破壊的メソッドじゃないRubyでは「sub!」で「!」が必要なところあたり注意が必要ですね($_自体が変更されないとダメなので)。

ruby -i'.bak' -pe '$_.sub!(/^(.*)$/, ",\\1")' tmpp.txt

さあ、これで変換終了です。慣れれば、10秒ぐらいで作業が終了しますw

あれ、題名が「Ruby ワンライナー」と言いながら、Rubyのところがあまりにも少ない(>_<)!

終了。

JRubyがやってくる(やあ、やあ、やあ)

Windowsで、Jrubyを使って(Tomcatも)、Redmine(Rails)を動かす

JRubyが誕生してだいぶ経ちます。しかし、やがてRuby2にも対応した正式リリースが間近、ということもあり、さらに、RailsではなくJRuby on Railsを選ぶ理由とは?あなたがCRubyではなくJRubyを使うべき理由にもあるように、JRubyが、むしろ、いけているようなんです。

そこで、JRubyでRailsで有名なRedmineをWindowsにインストールしてみました。以下がそのメモ書きです。

Javaのインストール

今回はMacBook AirにParallelを使って、Windows8.1がインストールされているので、こちらにインストールしてみます。

※私のMacBookAir5,2スペック

項目
機種名 MacBook Air
機種ID MacBookAir5,2
プロセッサ名 Intel Core i7
プロセッサ速度 2 GHz
プロセッサの個数 1
コアの総数 2
二次キャッシュ(コア単位) 256 KB
三次キャッシュ 4 MB
メモリ 8 GB

ParallelのWindows8.1を立ち上げておき、そのIEでjdk-7u67-windows-x64.exeからダウンロードして、ファイルを実行します。Program Filesにインストールされるのがちょっと嫌なので、

C:/jdk1.7.0_67/

にインストールさせました(もちろんデフォルトでもOK)。JAVA_HOMEとかの環境変数は設定されているので、これで終了。

MySQLのインストール

次に、Redmineで利用するDBをMySQLとすると、MySQL Community Server 5.6.20からダウンロードしてきて、実行します。すべてデフォルでインストールしても大丈夫だと思います。

ただ、最後にrootのパスワードを聞いていきますので、しっかり覚えておいて、後で書き込む必要があります。もしくは、ユーザーaddできるので、addした場合はユーザー名とパスワードを覚えておいてください。

Tomcatのインストール

Tomcatもインストールします。32-bit/64-bit Windows Service Installer (pgp, md5)をダウンロードして、実行します。これもまたデフォルトでOKですが、私は

C:\Tomcat 8.0

にインストールしました(そこだけデフォルトにあらず)。

JRubyのインストール

次に、Jrubyをインストールします。JRuby 1.7.13 Windows Executable (x64) (md5, sha1)からファイルをダウンロードして、実行します。これもデフォルトでOKでしょう。

試しに、Dosプロンプトで

C:\Users\chikkun>jruby -v
jruby 1.7.13 (1.9.3p392) 2014-06-24 43f133c on Java HotSpot(TM) 64-Bit Server VM
1.7.0_67-b01 [Windows 8.1-amd64]

とバージョンが出れば、完了。

Redmineのダウンロード

次はRedmineのダウンロード。redmine-2.5.2.tar.gzからダウンロードしたファイルを解凍させます。どこでもOKですが、とりあえずできたディレクトリーを

c:\redmine-2.5.2

に移動させます。

Railsに必要なもののインストール

そして、コマンドプロンプトで次のようなコマンドを1つ1つ実行していきます。

cd c:\redmine-2.5.2
jruby -S gem install rails
jruby -S gem install jruby-openssl
jruby -S gem install activerecord-jdbcmysql-adapter
# jruby -S gem install activerecord-jdbcpostgresql-adapter#必要に応じて
jruby -S gem install warbler

若干時間がかかりますが、気長に待ちましょう。

インストールが終わったら

c:\redmine-2.5.2\config\database.yml.example

c:\redmine-2.5.2\config\database.yml

としてコピーし、次のように書き換えます。MySQLのインストールの最後で設定したrootのパスワード、もしくは、ユーザーをaddしたのであれば、そのユーザー名とパスワードにします(下の例はrootです)。

production:
adapter: mysql2
database: redmine
host: localhost
username: root
password: "*****"
encoding: utf8

development:
adapter: mysql2
database: redmine_development
host: localhost
username: root
password: "*****"
encoding: utf8

test:
adapter: mysql2
database: redmine_test
host: localhost
username: root
password: "*****"
encoding: utf8

そして、以下のコマンドを、やはり、DOSプロンプトで実行します。カレントディレクトリは「c:\redmine-2.5.2」です。

jruby -S bundle install --without test development
jruby -S rake db:create RAILS_ENV=production
jruby -S rake db:migrate RAILS_ENV=production
jruby -S bundle exec rake generate_secret_token
Prepending `bundle exec` to your command may solve this.

が出たらGemfileのrakeのバージョンを上げて

jruby -S bundle update rake

とすると良いかもしれません(今回は出ませんでしたが、以前やったときは出ました)。

warble config

そうすると、以下のようなファイルが作成されています。

c:\redmine-2.5.2\config\warble.rb

この中身はすべてコメントアウトされていますが、以下をコメントをはずして、少々書き換えます。

config.gems += ["activerecord-jdbcmysql-adapter", "jruby-openssl", "i18n", "rack"]
config.webxml.rails.env = 'production'
config.webxml.jruby.compat.version = "1.9"

あとは、warファイルを以下のようにして作成するだけです。

warble

DOSプロンプトで実行すれば、

redmine-2.5.2.war

が作成されますが、これを

redmine.war

にリネームします。そして、これをTomcatをインストールした

C:\Tomcat 8.0\webapps

にコピーします。そしてデスクトップのタスクトレイにある、下の図の左下に見える三角印のアイコンを右クリックして、Stop→Startを行ってください(Tomcatの再起動)。

tomcat_icon

そしてブラウザで下のURL

http://localhost:8080/redmine/

にアクセスして、次の画面が見えたら、無事インストール終了です。
redmine

WordPress ウィジェット を作る

WordPress ウィジェット を作る

今回は、ウィジェットの作り方です。

ウィジェットはサイドバーに表示される小さめのプラグインといった感じですね。実際、ウィジェットもプラグインとして作るわけです。ウィジェットを作ってみてそれからプラグインに入るといろいろわかりやすいのではないかと思いました。

いつものようにWordPress CodexのWordPress ウィジェット APIなどを参考にさせていただいています。

ウィジェットはプラグインとして作る

ウィジェットはプラグインとして作ります。プラグインを作るには、プラグインメインファイルの冒頭にコメントとして次のような情報を書き込んでおけば良いのでした。

<?php
/*
Plugin Name: (プラグインの名前)
Plugin URI: (プラグインの説明と更新を示すページの URI)
Description: (プラグインの短い説明)
Version: (プラグインのバージョン番号。例: 1.0)
Author: (プラグイン作者の名前)
Author URI: (プラグイン作者の URI)
License: (ライセンス名の「スラッグ」 例: GPL2)
*/

これを記述したファイルを含むディレクトリをwp-contents/plugins/配下に置くと、WordPressによってプラグインと認識されます。管理者としてログインし、プラグインメニューを開くと一覧の中にプラグインの名前とその情報が表示されていることでしょう。

そのプラグイン(ウィジェット)を有効化すれば、管理メニューの「外観」->「ウィジェット」の中にあなたのウィジェットを見つけることができます。

Widget作成

プラグインではなくウィジェットにするには、WordPressのWP_Widgetクラスを継承したPHPクラスを作ります。そうすることで簡単にウィジェットにすることができます。基本的には、4つのメソッドをオーバーライドするだけです。構成は次のようになります。

なお、後ほど見るように管理者がウィジェットのために設定する項目は、WordPressのデータベースに登録されます。

class CD_My_Widget extends WP_Widget {

          // constructor
          function CD_My_Widget() {
                    /* ... */
          }

          // widget form :ウィジェットの設定画面
          function form($instance) {
                    /* ... */
          }

          // widget update :データベースに登録する値の処理
          function update($new_instance, $old_instance) {
                    /* ... */
          }

          // widget display :ウィジェットとして表示する内容の記述
          function widget($args, $instance) {
                    /* ... */
          }
}

// register widget :
add_action('widgets_init',  function() {
        register_widget("CD_My_Widget"); // WP_Widget を継承したクラス名を登録
    }
);

add_actionの第一引数にフックとして「widgets_init」を指定することで、widgetの初期化の時に第二引数の「文字列で指定された」関数が(フックされたタイミングで)実行されます(引数は文字列であることに注意)。

上の例では、関数名を指定する第二引数に無名関数を定義しています。無名関数は変数へ代入できるのでこのような使い方ができます。

add_action('widgets_init',  function() { register_widget("CD_My_Widget"); });

は、下のものと同等です。

add_action('widgets_init',  'hoge');

function hoge() { register_widget("CD_My_Widget"); }

このようにして、フックのタイミングでregister_widget("…")関数が呼び出され、クラス名「CD_My_Widget」を登録します。

次に、ウィジェットの各メソッドを見ていきましょう。

  1. コンストラクタ: function CD_My_Widget() { // }
  2. 設定を登録する管理画面フォーム: function form($instance) { // }
  3. データベースに登録する値を処理する:function update($new_instance, $old_instance) { // }
  4. ウィジェットコンテンツを表示する: function widget($args, $instance) { // }
コンストラクタ

継承した親のコンストラクタを呼び出し、このウィジェットにはcd_widgetという名前を付けました。このウィジェットのクラス名はCD_My_Widgetなので、コンストラクタは、

    function CD_My_Widget() {
        parent::WP_Widget(false, $name = 'cd_widget');
    }

となります(これはphp4の書き方なのですね。php5風に__construct()としても大丈夫です)。

ここでは先ず、最低限の内容として「タイトル」と「テキスト」を入力し、登録、表示させるウィジェットを完成させることにします。その際、それぞれに‘title’‘text’という(自分で決めた)キーを用いています。

設定を登録する管理画面フォーム

管理画面でウィジェットをサイドバーにドラッグ&ドロップした場所で表示される設定画面です。

    function form($instance) {
        if( $instance) {
            $title = esc_attr($instance['title']);
            $text = esc_attr($instance['text']);
        } else {
            $title = '';
            $text = '';
        }
    ?>
        <p>
            <label for="<?php echo $this->get_field_id('title'); ?>">
                <?php _e('Title:'); ?>
            </label>
            <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
                   name="<?php echo $this->get_field_name('title'); ?>"
                   type="text"
                   value="<?php echo $title; ?>" />
        </p>

        <p>
            <label for="<?php echo $this->get_field_id('text'); ?>">
                <?php _e('Text:'); ?>
            </label>
            <input class="widefat" id="<?php echo $this->get_field_id('text'); ?>"
                   name="<?php echo $this->get_field_name('text'); ?>"
                   type="text"
                   value="<?php echo $text; ?>" />
        </p>

    <?php
    }

既に登録されている値があれば、「< > & ” ‘ (小なり、大なり、アンパサンド、ダブルクォート、シングルクォート)」 文字参照をエンコードして表示しています。

データベースに登録する値を処理する
    function update($new_instance, $old_instance) {
        $instance = $old_instance;

        $instance['title'] = strip_tags($new_instance['title']);
        $instance['text'] = strip_tags($new_instance['text']);

        return $instance;
    }

新しく登録する値からHTMLタグを剥ぎ取りハッシュで返します。設定値は「wp_optionsテーブル」に1レコードとして登録されます。

ウィジェットコンテンツを表示する
    function widget($args, $instance) {
        extract( $args );
        $title = apply_filters('widget_title', $instance['title']);
        $text = $instance['text'];

        echo $before_widget;
        // ウィジェット本体を囲む
        echo '<div class="widget-text cd_my_widget_block">';

        // 「タイトル」の表示
        if ( $title ) {
            echo $before_title . $title . $after_title;
        }

        // 「テキスト」の表示
        if( $text ) {
            echo '<p class="cd_my_widget_text">'.$text.'</p>';
        }

        echo '</div>';
        echo $after_widget;
    }

タイトルとテキストを表示するだけですが、立派にウィジェットが完成しました。

ちなみに、さりげなく使われている$before_title$after_title$before_widget$after_widgetといった変数は、引数の$argsextractしてできた変数です。

CSSファイルを読み込む

スタイルシートで表現をカスタマイズしたいですね。

スタイルシートを読み込むには、

    wp_register_style('識別子', 'cssファイルへのパス', array('依存する他の識別子'), 'ヴァージョン番号');
    wp_enqueue_style('識別子');

この2つの関数で実現できます。まずはwp_register_style関数で登録だけしておいて、そのスタイルシートを使う場面でwp_enqueue_style関数で読み込ませる、という使い方ができます。

wp_register_style関数
    wp_register_style(  $handle, $src, $deps = array(), $ver = false, $media = 'all'  );

$handle … スタイルシートを登録する「識別子」。初期値: なし。

$src … このスタイルシートのファイル名(パス付き)。初期値: false 。

$deps … このスタイルシートが依存していて、これよりも先に読み込まれていなければならないスタイシートの「識別子」を配列で記述する。初期値: array() 。

$ver … このスタイルシートのヴァージョン番号。初期値: false 。

$media … どの種類のmediaに対するスタイルシートなのかを指定する。例: ‘all’, ‘screen’, ‘handheld’, ‘print’。 初期値: ‘all’ 。

wp_enqueue_style関数
    wp_enqueue_style( $handle, $src = false, $deps = array(), $ver = false, $media = 'all' )

引数は、wp_register_styleと全く同じです。ソースを読むとwp_enqueue_style関数だけで引数があればスタイルシートの登録と同時に読み込ませられることがわかりました。確かめてみたら

    wp_enqueue_style('cd_jp_calendar_style', plugins_url('', __FILE__) . '/cd_jp_calendar.css', array(), '201406');

とすることでスタイルシートを読み込ませることができました。

いつスタイルシートを読み込ませるか

使いもしないのにスタイルシートだけを読み込ませておくのは無駄です。どのタイミングで読み込ませるかを指定するのがadd_action関数のフックですね。

    add_action( 'admin_init', '実行するメソッド');
    add_action( 'admin_menu', '実行するメソッド');
    add_action( 'wp_enqueue_scripts', '実行するメソッド');

admin_initフックは、管理画面が表示される時一番初めに実行する関数を登録します。

admin_menuフックは、管理画面が表示される時メニューまたはサブメニューの表示のされ方を指定する関数を登録します。

wp_enqueue_scriptsフックは、コンテンツが表示される時に読み込ませるフックです。

ここではスタイルシートについてだけ述べていますが、javascriptファイルを読み込ませる時もほとんど同じです。wp_register_stylewp_register_scriptに、wp_enqueue_stylewp_enqueue_scriptに置き換えるだけです。

add_action('wp_enqueue_scripts', 'cd_enqueue_scripts');

function cd_enqueue_scripts() {
    wp_enqueue_style('cd_my_widget_style', plugins_url('', __FILE__) . '/cd_my_widget.css', array(), '201406');
}
Widget 完成形

クラスの内部も記述した全体です。

<?php
/*
  Plugin Name:    CD Test Widget
  Plugin URI:     http://www.co-machi.org/plugins/cd_my_widget/
  Description:     テストウィジェットです。
  Author:         Denn
  Author URI:     http://www.co-machi.org/
  Version:         201406
  License:        GPL2
 */

if (!class_exists('CD_My_Widget')) :
    require_once WP_PLUGIN_DIR . '/cd_my_widget/CD_Foge.php';

    class CD_My_Widget extends WP_Widget {

        // constructor
        function CD_My_Widget() {
            parent::WP_Widget(false, $name = 'cd_widget');
        }

        // widget form :ウィジェットの設定画面
        function form($instance) {
            if ($instance) {
                $title = esc_attr($instance['title']);
                $text = esc_attr($instance['text']);
            } else {
                $title = '';
                $text = '';
            }
            ?>
            <p>
                <label for="<?php echo $this->get_field_id('title'); ?>">
                    <?php _e('Title:'); ?>
                </label>
                <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
                       name="<?php echo $this->get_field_name('title'); ?>"
                       type="text"
                       value="<?php echo $title; ?>" />
            </p>

            <p>
                <label for="<?php echo $this->get_field_id('text'); ?>">
                    <?php _e('Text:'); ?>
                </label>
                <input class="widefat" id="<?php echo $this->get_field_id('text'); ?>"
                       name="<?php echo $this->get_field_name('text'); ?>"
                       type="text"
                       value="<?php echo $text; ?>" />
            </p>

            <?php
        }

        // widget update :データベースに登録する値の処理
        function update($new_instance, $old_instance) {
            $instance = $old_instance;

            $instance['title'] = strip_tags($new_instance['title']);
            $instance['text'] = strip_tags($new_instance['text']);

            return $instance;
        }

        // widget display :ウィジェットとして表示する内容の記述
        function widget($args, $instance) {
            extract($args);
            $title = apply_filters('widget_title', $instance['title']);
            $text = $instance['text'];

            echo $before_widget;
            // ウィジェット本体を囲む
            echo '<div class="widget-text cd_my_widget_block">';

            // 「タイトル」の表示
            if ($title) {
                echo $before_title . $title . $after_title;
            }

            // 「テキスト」の表示
            if ($text) {
                echo '<p class="cd_my_widget_text">' . $text . '</p>';
            }

            echo '</div>';
            echo $after_widget;
        }
    }

    endif;

// register widget
add_action('widgets_init', function() {
    register_widget("CD_My_Widget");
});

// register stylesheet
add_action('wp_enqueue_scripts', 'cd_enqueue_scripts');

function cd_enqueue_scripts() {
    wp_register_style('cd_my_widget_style', plugins_url('', __FILE__) . '/cd_my_widget.css', array(), '201406');
    wp_enqueue_style('cd_my_widget_style');
}

今回のウィジェットでは必要ありませんでしたが、別ファイルで定義した関数やクラスを使うためにインクルードする一行も入れてあります。

    require_once WP_PLUGIN_DIR . '/cd_my_widget/CD_Foge.php';

以上、ウィジェットの作り方でした。

プラグインより簡単に作れそう、と思っていただけたらうれしいです。

WordPressのウィジェットで、祝祭日対応カレンダーを作ってみました

WordPressのウィジェットで、祝祭日対応カレンダーを作ってみました。

マウスをホバーさせていない状態

祝祭日は赤数字で表示し、マウスをホバーさせると祝日の名前が浮かび上がります。

マウスをホバーさせた状態

また、投稿があった月では、その月の投稿の一覧表示画面へのリンクを付け(「平成26年5月」のところ)、投稿があった日には、その日の投稿へのリンクをつけました(上図でbackgroundが黄色になっているところ)。これはWordPressデフォルトのカレンダーにある機能を真似ました。前の月、次の月への切り替えもできるようにしました。

管理者での、ウィジェット設定画面で出来ることは、

ウィジェット設定画面

  1. 年の表示を元号(明治・大正・昭和・平成)と西暦のどちらかに指定できる。デフォルトは西暦。
  2. 月の表示を「*月」(デフォルト)、「和暦(睦月、如月、……)」、「英語(January,February,……)」にできる。
  3. 今月の何ヶ月前から表示するかを指定できる。
  4. 今月の何ヶ月後までを表示するかを指定できる。
  5. 西暦何年のカレンダーを表示するかを指定できる。

という、思いつくままに機能をつけた、まったく手前勝手な仕様です。
このカレンダーの設定はウィジェットに使うものではないだろう、というご指摘は全くその通りですね。

画像で施している設定は、今月の6か月前から今月の1ヶ月後までのカレンダーを表示させるというものです。

その設定ではうるさすぎるので現在は、デフォルトの設定にしてあります。ちょうど右側あたりに見えているのではないかと思います。

というわけで(なにがだ?)、「WordPressのWidgetを作る」の予告編でした。

みなさんもウィジェット作りに挑戦してみませんか。

作り方は次回の投稿で説明する予定です。

UbuntuをVirtual Boxに

MacにUbuntu(日本語版)をインストール

OSX 10.9.2にUbuntuをインストールした時のメモ(Windowsでもほぼ同じだと思います)。UnbutuにPHPが使える環境も整えます(簡単なのでメモするほどのこともありませんが・・・)。

まずはVirtual Boxをダウンロードして、インストール

https://www.virtualbox.org/wiki/Downloadsへ行って、最新の自分のOSに合わせたものをダウンロードします。私のマシーンはMacなので上から2番目のVirtualBox 4.3.10 for OS X hostsをダウンロードしました(114Mがあります)。

Windowsの場合は、exeファイルなのでそれを実行すれば、インストールされるんでしょうが、Macの場合はdmgファイルなので、これをマウントして、中身の「VirtualBox.pkg」を実行します。すると、インストールが開始されます。

Ubuntuのダウンロードとインストール

http://www.ubuntulinux.jp/japaneseに行って、仮想ハードディスクイメージのダウンロードをダウンロードします。

私の場合http://www.ubuntulinux.jp/download/ja-remix-vhdで「ubuntu-ja-12.04-desktop-i386-vhd.zip」をダウロード(1Gもあります!)。

インストールの方法は上記のhttp://www.ubuntulinux.jp/download/ja-remix-vhdに画像付きで説明してありますが、要するに次のように行います。

  1. ダウンロードしたファイルを解凍する(解凍すると3.55Gあります—ubuntu-ja-12.04-desktop-i386.vhd)。
  2. 解凍したファイルをお好きな場所に移動させる(フラッシュメモリというような選択肢も)。
  3. 先ほどインストールしたVirtual Boxを起動します。
  4. 新規をクリックし、名前は適当に入れて、「タイプ」には「Linux」、「バージョン」は「Ubuntu(64 bit)」を選択して、「次へ」ボタンをクリック。
  5. 割り当てるメモリを決めます。後からでも変えられるので、ここではとりあえず1G(1024M)とします。
  6. [ハードドライブ]ダイアログでは、「すでにある仮想ハードドライブファイルを使用する」を選択し、 2でどこぞに移動させてた「ubuntu-ja-12.04-desktop-i386.vhd」を選択します。
  7. あとは上にある「→」の起動ボタンをクリックします。
  8. Ubuntuの最初の起動では、言語を聞いてくるので「日本語」を選択し、「どこに住んでいますか」はデフォルトである「Tokyo」を、キーボードレイアウトはWindowsだったら「日本語/日本語」、Macなら「日本語/日本語(Macintosh)」を選択します。
  9. 「あなたの情報を入力して下さい」で、あなたの名前は適当に「Sakai」とか入力し、「コンピュータの名前」も適当に「Ubuntu」、ユーザ名とパスワードは半角で適当に入れましょう(これはログインで必要になるので、忘れないように!)。また、セキュリティをあまり気にしないのであれば(テスト用だから)、「自動でログインする」を選択しておけば、楽ちんです。

これで、Ubuntuが起動できるはずです。

余談ですが、画面の一番上にあるVirtual Boxのメニューの「Settings」を選び、Advancedタブを選択し、「Shared Clipboard」と「Drag'n'Drop」の両方を「Bidirectional」を選択し、再起動すると、本来のOSからUbuntuへコピー&ペーストが出来るようになります(しかし、何故か私のMacではダメです・涙)。

LAMPをインストール

  1. Ubuntuの左上にあるボタンをクリックすると、検索するダイアログが開かれます。そこで「terminal」と打ち込むと、黒っぽいアイコンが現れるのでそれをクリック。
  2. WindowsでいうDosプロンプトみたいなものが開きますので、taskselというものをインストールします。
    sudo apt-get install tasksel

    を実行します。パスワードが聞かれますが、インストールの時に入力したものを入力します。「y/n」が出た場合はいつも「y」を入力し、リターンを押下します。

  3. 次にLAMPをインストールします。
    sudo tasksel install lamp-server
  4. MySQLのパスワード設定
    mysqladmin password xxxxxxx -u root -p

    パスワードを聞かれますが、たぶん、Ubuntuの時のパスワードを入れて、リターンすると「xxxxxxx」(これは適宜変えて下さい)変更されます。「たぶん」とは、僕は少なくともそれでOKだったという根拠しかないので、スミマセン。ただ、以前はインストール中にrootのパスワードを入れろって言われた気がしますが、今回はありませんでした。

さて、これでターミナル(bash)の中で

php -v

と打ち込みリターンを押下して、バージョンが表示されたらとりあえずOKです。

これで、あっけなく終了。

ちなみに、Ubuntuを終了させるには一番右上端のギヤーみたいなものをクリックすると、シャットダウンが見えますので、それをクリックすると、終了します。

WordPressのプラグイン作成 第二回

WordPress のプラグイン作成 — $wpdb 編 その1 –

posted at 2014/01/15 by denpoya@コワーキングスペース町田

今回は、WordPressにデータベースのテーブルを作成してもらうことが主なテーマです。その時使う$wpdbやWordPressの関数dbDelta()update_option()get_option()の使い方にも触れておきます。

さらに、テーブルの数が複数になった時にforeach文で回すための工夫もしておくことにしましょう。バージョンが上がって、テーブルを増やさなければならなくなった時でも簡単に対処できるようになるでしょう。

WordPressにテーブルを作ってもらう

WordPressにテーブルを作ってもらうのはいつどの場面でしょうか?プラグインを「有効化」した時の1回だけでよいですか?

プラグインの新しいバージョンを作成する際、別のテーブル構造が必要になったらアップグレード用関数を作成することになるでしょう。フィールド属性を変更するだけでなく、新しいテーブルを追加することもあるかもしれません。そのようなことを念頭に置いてコーディングしていきます。

WordPressが通常使っているテーブルではなく、私たちのプラグイン専用のテーブルを「有効化」のタイミングで作成することにします。本当に専用のテーブルが必要かどうかは検討すべきでしょう。このプラグインには必要である、という設定でお話を進めていきます。

WordPressをインストールするときにテーブル名のプレフィックス(接頭辞)を指定できました。私たちのテーブル名にも同じプレフィックスをつける必要があるでしょう。デフォルトは「wp_」ですがインストールする人によって異なるプレフィックスになっているかもしれません。

まず、第1段階として

  1. 「テーブル名に付けられているプレフィックスを私たちのテーブルにも付ける」
  2. 「私たちのテーブルが既に存在しているかを確認しておく」

この2つからコーディングしていきます。必要なものは$wpdbです。

WordPressのデータベース操作用のクラス関数 wpdb

WordPressにはデータベース操作用のクラス関数が用意されています。

しかしそれを直接使うのではなく、WordPressにはデータベースと対話するために設定されたクラスをインスタンス化した$wpdbというグローバル変数が用意されているのでこのグローバル変数$wpdbを使うのが良いようです。

そのためにはグローバル宣言をします。

global $wpdb;

$wpdbの持つメソッドのうちよく使うのが、get_var('query',column_offset,row_offset)get_results('query', output_type)そしてquery('query')といったSQL文を発行するメソッド、その時にSQLインジェクション攻撃からクエリを保護するために使うprepare( 'query'[, value_parameter, value_parameter ... ] )メソッドとSQLエスケープのためのescape($user_entered_data_string)ではないでしょうか。説明は関数を使う時にしましょう。

テーブル名にプレフィックスをつける

テーブル名に付けられているプレフィックスは、

$wpdb->prefix;

という変数に格納されています。例えば、「example_table」という名前にプレフィックスをつけてテーブル名にしたいならば、

$table_name = $wpdb->prefix . 'example_table';

のようにします。

テーブルが既に存在しているかどうかを確認する

テーブルが既に存在しているかどうかを確認するのには、

$is_db_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));

のように「SHOW TABLES LIKE テーブル名」クエリを発行します。prepareメソッドによって、「%s」には第2引き数の文字列がその箇所に挿入されます(2つ目の「%s」があったら第3引き数の値が挿入されます)。

get_varメソッドはデータベースから変数を一つ返します。変数は一つしか返ってきませんが、クエリの結果はあとから使うことができます。結果にマッチするものがない場合、NULL が返されます(関数リファレンス/wpdb Classを参照、以下同様)。

テーブルがないときには作成するようにします。

CDPluginクラスのactivate()メソッドを実装

前回作成したCDPluginクラスのactivate()メソッドを編集します。既に述べたことから下のようになるでしょう。

  • CDPlugin.php
<?php
class CDPlugin {
    function __construct() {
    }

    function activate() {
        $table_name = $wpdb->prefix . 'main_master';
        $is_db_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));

        if ($is_db_exists == $table_name) {
            return;
        }

        // ここでSQLクエリの実行する

    }
}
SQLクエリの実行

直接SQLクエリを実行($wpdb->query($sql))してもよいのですが、wp-admin/include/upgrade.phpで定義されているdbDelta関数を使う方がよいようです。dbDelta関数現在のテーブル構造を走査し、作成予定のテーブルと比較、必要に応じてテーブルを追加・修正します。更新にはとても便利な関数です(Creating Tables with Plugins(日本語版)を参照)。ただし、使用するには少しばかり癖があるようです。

参考にさせていただいた上記リンクとその最新版であるCreating Tables with Plugins(最新英語版)のページを見ると日本語版よりも癖がもう一つ増えて4つ項目が並んでいます。

  1. 1行につき1つのフィールドを定義してください。 〔訳注:一つの行に複数のフィールド定義を書くことは出来ません。さもなくば ALTER TABLEが正しく実行されず、プラグインのバージョンアップに失敗します。 〕
  2. PRIMARY KEYと主キーの定義の間には二つのスペースが必要です。〔訳注:原文 "You have to have two spaces between the words PRIMARY KEY and the definition of your primary key."〕
  3. INDEXという言葉ではなく、KEYという言葉を使う必要があります。
  4. フィールド名をアポストロフィやバッククォートで囲ってはいけません。〔原文:"You must not use any apostrophes or backticks around field names. "〕

このことに気を付けてSQL文を次のように作ります。dbDelta関数を使うには、wp-admin/include/upgrade.phpをインクルードする必要があります。

発行するSQL文を次のようにし、dbDelta関数を実行させます。

$sql = "CREATE TABLE " . $table_name . " (
     id mediumint(9) NOT NULL AUTO_INCREMENT,
     name tinytext NOT NULL,
     url VARCHAR(55) NOT NULL,
     created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '作成日',
     modified timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '修正日',
     UNIQUE KEY id (id)
     );";

require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
バージョン管理

合わせて私たちのテーブルのバージョン管理の仕組みも用意します。プラグインを一度「停止」してもらい、再び「有効化」することで今編集しているactivate()が実行されるのでデータベースに変更があった時だけdbDelta関数を呼び出しテーブルが追加更新されるようにするためです。

私たちのテーブルのバージョン番号は、WordPressの持つ設定を保存するテーブルに登録させてもらうことにしましょう。

独立した、名前の付いたデータ (「設定」) を WordPress データベースに保存し利用することができます。設定値の取り得る形式は、文字列 (string)、配列 (array)、もしくは PHP オブジェクトです (オブジェクトは、保管時は「シリアライズ」もしくは文字列に変換され、読出時にオブジェクトに戻されます)。オプション名は文字列で、他にない唯一のものでなければなりません。なぜなら、WordPress や他のプラグインと衝突しないためです(プラグインの作成を参照)。

使う関数は下の2つ、add_option()を利用する必要はありません。

update_option( $option, $new_value )

optionsデータベーステーブルから、指定したオプションの設定値を更新または作成します。オプションの設定値はインサートされる前に$wpdb->escapeでエスケープされます。また、指定された
オプション名が存在していないときにはadd_option( $option, $new_value )が実行されるので、add_option( $option, $new_value )の代りに使うことができます。

$option_name
    (文字列) (必須) 更新したい設定の名前。有効なデフォルトオプション値は、Option Referenceに一覧があります。

        初期値: なし

$newvalue

    (混合) (必須) 設定の新しい値。この値には、整数、文字列、配列やオブジェクトを取ることができます。

        初期値: なし
get_option( $show, $default )

optionsデータベーステーブルから、指定したオプションの値を取得する安全な方法です。希望するオプションが存在しない場合は、値が関連付けされず、FALSE が返されます。

 $show
    (文字列) (必須) 取得するオプションの名前。有効なデフォルトオプション値は、Option Referenceに一覧があります。

        初期値: なし

$default
    (混合) (オプション) 値が返されない(データベースにオプションが存在しない)場合のデフォルト値。

        初期値: false
完成したメソッド

SQL文は、別メソッドからの戻り値となるようにしました。

<?php
class CDPlugin {
    // 現在のテーブルのバージョン。
    // 数値を上げるとテーブルを更新する。
    // (テーブルが存在していて)上げなければ何もしない。
    private $version = 0.1;

    function __construct() {
    }

    public function activate() {
        $db_version = get_option('cdq_db_version', 0);

        $table_name = $wpdb->prefix . 'main_master';
        $is_db_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));

        if ($is_db_exists == $table_name && $db_version >= $this->version) {
            return;
        }

        require_once ABSPATH . "wp-admin/includes/upgrade.php";
        dbDelta($this->$getSql($table_name));

        update_option("cdp_db_version", $this->version);
    }

    private function getSql($table_name) {
        return <<<EOS
CREATE TABLE  $table_name (
     id mediumint(9) NOT NULL AUTO_INCREMENT,
     name tinytext NOT NULL,
     url VARCHAR(55) NOT NULL,
     created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '作成日',
     modified timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '修正日',
     UNIQUE KEY id (id)
);
EOS;
    }

}

複数のテーブルに対応

テーブルが1つだけなら良いのですが、複数のテーブルを使うプラグインでは、先のactivate()がテーブルの数だけ必要になります。そんな時はforeachで回したくなります。そのための工夫をしてみましょう。

テーブル名の識別とSQL文

先ず、テーブルを識別できるようにします。新しいテーブルを追加するときにここに追加記入します。

private $tables = array(
    "master_1",
    "detail_1",
    "master_2",
    "detail_2",
);

そしてもう一つ新しいテーブルを追加するときに記述しなければならないのはSQL文です。この二つを記述するだけで新しいテーブルを追加できるようにします。

それには、SQL文を返すメソッド名に規則を作ります。すなわちメソッド名に、上で記入したテーブルの識別を使って、例えば、こんな感じにします。

private function master_1Sql($table_name) {return "CREATE TABLE $table_name ・・・"}
private function detail_1Sql($table_name) {return "CREATE TABLE $table_name ・・・"}
private function master_2Sql($table_name) {return "CREATE TABLE $table_name ・・・"}
private function detail_2Sql($table_name) {return "CREATE TABLE $table_name ・・・"}

"テーブルの識別" . "Sql"がメソッド名になっています。新しいテーブルを追加するときに新たに記述するのはこれだけでそれ以外に修正する必要はありません。

プレフィックスをつける

実際にCREATEされるテーブル名(プレフィックスがつく)を作ります。

private function getTableNames() {
    global $wpdb;
    foreach ($this->tables as $name) {
        $this->table_names [$name] = $wpdb->prefix . $name;
    }
    return $table_names;
}
クエリを実行

そして、各テーブルごとにSQL文を発行し、クエリを実行するよう以下のコードをactivate()メソッドに埋め込みます。

require_once ABSPATH . "wp-admin/includes/upgrade.php";

$table_names = $this->getTableNames();
foreach ($table_names as $name => $table_name) {
     $sqlfunc = $name . "Sql";
    dbDelta($this->$sqlfunc ($table_name));
}

今回は、ここまで。

次回は、「$wpdb 編 その2」として、$wpdbの使い方、エラー処理(といっても、エラーメッセージを受け取るわけですが)などに触れたいと思います。

Ruby On Rails4 の開発を始められるまで その1

Ruby On Rails4 の開発を始められるまで その1

posted at 2014/01/14 by chikkun@コワーキングスペース町田

Ruby On Rails4の基本を、自分のメモも兼ねて、3回シリーズでご報告します。

今日はその第1回目(2回目はdeviseを使った認証機能の追加。3回目はRspecやCucumberを利用したテストの予定です)。

Railsを使って開発する直前までをまとめます。ただ一応、環境と目標とするのは以下のようです。OSの違いや、仮に同じOSでも微妙にバージョンで違ったりする場合もあるのでご注意下さい。

  1. OSはCentOS6.5とする(MarvericksにParallelsを利用して、SDカードにインストールしたもの—ほとんど何もインストールされていない)。
  2. アプリ名は「comachi」。
  3. Apache2+Passengerを利用し、標準で付いているWEBrickは利用しない。
  4. Railsは4とする。
  5. DBはPostgreSQLを利用する。
  6. 外見はTwitter Bootstrapを使用する。
rbenvのインストール

CentOS6ですとRubyが2系ではないので、まずはRuby2をインストールします。というのもRails4はRuby2を推奨しているからです。

ただ今後1.9等も必要になることが予想されるので、便利なrbenvを使ってRubyのバージョンを変えられるようにします。ただrbenvをインストールする前に、RubyやPassenger等で必要になりそうなものを事前にyumでインストールします。

$ yum -y install  yum-plugin-priorities zlib zlib-devel readline readline-devel openssl openssl-devel libxml2 libxml2-devel libxslt libxslt-devel httpd httpd-devel curl-devel bison gcc gcc-c++ git emacs
  • ※↑は1行です。
  • ※最後のemacsは、好みでvimなどでもw
  • ※上記のいくつかは、gitやapache、Passengerのインストールに必要なもの等も入っています。Passengerの場合は必要なものをインストール時に教えてくれるので、仮に足らなくても大丈夫です。
  • ※CentOSのバージョンによっては「–enablerepo=remi,epel」オプションを入れる必要があるかもしれません(6.5では必要はありませんでした)。

これで準備が出来たので、早速rbenvのインストールです。

# 必要に応じてsuなりsudoをして下さい
$ cd /usr/local
$ git clone git://github.com/sstephenson/rbenv.git rbenv
$ groupadd rbenv
$ chgrp -R rbenv rbenv
$ chmod -R g+rwxXs rbenv
$ cd rbenv
$ mkdir plugins
$ cd plugins
$ git clone git://github.com/sstephenson/ruby-build.git ruby-build
$ chgrp -R rbenv ruby-build
$ chmod -R g+rwxs ruby-build
環境変数の設定

rbenvのインストールは終わりましたが、環境変数を設定しないと使えません。

そこで環境変数を/etc/profileに設定します(「/etc/profile」を直接編集したくない場合は「/etc/profile.d/rbenv.sh」等に書くのも手です)。

$ emacs /etc/profile #viでも

で、一番下に以下のように環境変数を設定します。

export RBENV_ROOT="/usr/local/rbenv"
export PATH="$RBENV_ROOT/bin:$PATH"
eval "$(rbenv init -)"

これを書き込んで、それら環境変数を読み込みます。

$ source /etc/profile
Autoconfのバージョンアップ

最新のRuby2.2-devのインストールでは、autoconfのバージョンが低いと怒られるので、m4とAutoconfの上書きバージョンアップを行います。

$ wget ftp://ftp.gnu.org/gnu/m4/m4-1.4.17.tar.gz
$ tar xvzf m4-1.4.17.tar.gz
$ cd m4-1.4.17
$ ./configure --prefix=/usr
$ make
$ make install
$ cd ..
$ wget ftp://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz
$ tar xvzf autoconf-2.69.tar.gz
$ cd autoconf-2.69
$ ./configure --prefix=/usr
$ make
$ make install
インストール可能なRubyの確認

インストール可能なRubyのパッケージを確認する。

$ rbenv install -l
...
  1.9.3-rc1
  2.0.0-dev
  2.0.0-p0
  2.0.0-p195
  2.0.0-p247
  2.0.0-p353
  2.0.0-preview1
  2.0.0-preview2
  2.0.0-rc1
  2.0.0-rc2
  2.1.0
  2.1.0-dev
  2.1.0-preview1
  2.1.0-preview2
  2.1.0-rc1
  2.2.0-dev
...

バージョンはお好みで。とりあえずここでは「2.2.0-dev」をインストールします。

$ rbenv install 2.2.0-dev #時間はそこそこかかります
$ rbenv rehash # これをやる必要があります
$ rbenv global 2.2.0-dev
インストールできたか確認
$ ruby -v
ruby 2.2.0dev (2014-01-12 trunk 44563) [x86_64-linux]

OKのようですw

PostgreSQLのインストール

yum.postgresql.orgに行って「pgdg-centos93-9.3-1.noarch.rpm」を持ってきて、rpmを実行します。

wget http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-centos93-9.3-1.noarch.rpm
rpm -ivh pgdg-centos93-9.3-1.noarch.rpm

すなわち、wgetでダウンロードし、rpmでrepositoryのファイルをインストールします。そして次にyumでPostgreSQLをインストールします。

yum install -y postgresql93-server postgresql93-devel postgresql93-contrib

次にお決まりの「initdb」をし、サーバーを立ち上げます。またOSを再起動しても自動で立ち上げるためにchkconfigし、ついでにapacheも自動起動するようにします。

さらに、postgresユーザのパスワードを作成し、新しいユーザ(chikkun)、データベース(comachi)を作成します。

$ /etc/init.d/postgresql-9.3 initdb
$ /etc/init.d/postgresql-9.3 start
$ chkconfig --level 345 postgresql-9.3 on
$ chkconfig --level 345 httpd on
$ su postgres
$ psql
% alter role postgres with password 'hogehoge'; #postgresユーザにパスワードを設定
% \q
$ createuser -P -s chikkun
Enter password for new role: 
Enter it again:
createdb comachi
$ exit

それから、/var/lib/pgsql/9.3/data/pg_hba.confを少々書き換えます。

local   all             all                                     peer
# IPv4 local connections:
host    all             all             127.0.0.1/32            ident
# IPv6 local connections:
host    all             all             ::1/128                 ident

のpeer、identをmd5にします。

local   all             all                                     md5
# IPv4 local connections:
host    all             all             127.0.0.1/32            md5
# IPv6 local connections:
host    all             all             ::1/128                 md5

そして再起動。

/etc/init.d/postgresql-9.3 restart

たぶん、PostgreSQLのRuby用のモジュールでインストールで失敗するので、先にオプションを付けて、インストールしていまいます。

gem install pg -- --with-pg_config=/usr/pgsql-9.3/bin/pg_config
  • ※上の「–」が余計に見えますが、これがないと「そんなオプションはないよ」と言われます(というか、言われ続けました・涙)。
Railsのインストール

さてさて、何はなくともRailsをインストールします。

$ gem install rails
$ rbenv rehash
$ rails -v
Rails 4.0.2

どうやら大丈夫そう。

passengerのインストール

次に、gemを使ってpassengerをインストールします。

gem install passenger
passenger-install-apache2-module

先回りして、必要なものをインストールしているので全く同じ環境ならば大丈夫ですが、環境が違っていたりすると、足らないものが出たりします。

しかし、そのような場合は、以下のようなメッセージで、必要なもののリストをyumコマンド付きで教えてくれるので、インストールしたら再度「passenger-install-apache2-module」を実行します。

Installation instructions for required software

 * To install C++ compiler:
   Please install it with yum install gcc-c++

無事コンパイルが始まって、終了すると以下のようなメッセージが出ます。このうち、最初の頃のLoadModuleの部分と、最後の辺りにあるVirtualHostの部分が必要になります。

The Apache 2 module was successfully installed.

Please edit your Apache configuration file, and add these lines:

   LoadModule passenger_module /usr/local/rbenv/versions/2.2.0-dev/lib/ruby/gems/2.2.0/gems/passenger-4.0.33/buildout/apache2/mod_passenger.so
   PassengerRoot /usr/local/rbenv/versions/2.2.0-dev/lib/ruby/gems/2.2.0/gems/passenger-4.0.33
   PassengerDefaultRuby /usr/local/rbenv/versions/2.2.0-dev/bin/ruby

After you restart Apache, you are ready to deploy any number of web
applications on Apache, with a minimum amount of configuration!

Press ENTER to continue.


--------------------------------------------

Deploying a web application: an example

Suppose you have a web application in /somewhere. Add a virtual host to your
Apache configuration file and set its DocumentRoot to /somewhere/public:

   <VirtualHost *:80>
      ServerName www.yourhost.com
      # !!! Be sure to point DocumentRoot to 'public'!
      DocumentRoot /somewhere/public    
      <Directory /somewhere/public>
         # This relaxes Apache security settings.
         AllowOverride all
         # MultiViews must be turned off.
         Options -MultiViews
      </Directory>
   </VirtualHost>

And that's it! You may also want to check the Users Guide for security and
optimization tips, troubleshooting and other useful information:

  /usr/local/rbenv/versions/2.2.0-dev/lib/ruby/gems/2.2.0/gems/passenger-4.0.33/doc/Users guide Apache.html

http://www.modrails.com/documentation/Users%20guide%20Apache.html

Enjoy Phusion Passenger, a product of Phusion (www.phusion.nl) :-)

https://www.phusionpassenger.com

Phusion Passenger is a trademark of Hongli Lai & Ninh Bui.

すなわち

LoadModule passenger_module /usr/local/rbenv/versions/2.2.0-dev/lib/ruby/gems/2.2.0/gems/passenger-4.0.33/buildout/apache2/mod_passenger.so
PassengerRoot /usr/local/rbenv/versions/2.2.0-dev/lib/ruby/gems/2.2.0/gems/passenger-4.0.33
PassengerDefaultRuby /usr/local/rbenv/versions/2.2.0-dev/bin/ruby

<VirtualHost *:80>
   ServerName www.yourhost.com
   # !!! Be sure to point DocumentRoot to 'public'!
   DocumentRoot /somewhere/public    
   <Directory /somewhere/public>
      # This relaxes Apache security settings.
      AllowOverride all
      # MultiViews must be turned off.
      Options -MultiViews
   </Directory>
</VirtualHost>

が必要になります。もっと具体的には、上のものはそのままhttpd.confにコピペ、下のものは自分の環境に合わせて書き換えた後にhttpd.comfに追記します。

実際には私の場合以下のようにしました。

<VirtualHost *:80>
    ServerAdmin hoge@fuga.com
    DocumentRoot /var/www/html/rails/comachi/public
    ServerName www.comachi.com
    ErrorLog logs/comachi.error_log
    CustomLog logs/comachi.access_log common
    RackEnv development
    <Directory /var/www/html/rails/comachi/public>
        Options -MultiViews
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>

という感じです。

  • ※実際にの「DocumentRoot」は違っていて、CentOS自体がSDカードに入っていて、そのSDカード自体にRailsアプリを置いてあるので、実際には/media/psf/UBUNTU/rails/publicで「UBUNTU」はSDカードのボリューム名です。

そこで、/etc/httpd/conf/httpd.confのLoadModuleを記載している最後に(今回の例では217行あたり)上記の3行を追加します。

そして、VirutualHostの設定はhttpd.confの最後に(ディストリビューションによっては別ファイルのことも)上記のを参考に追記します。

ここではrailsのアプリの場所が「/var/www/html/rails/comachi」としていますが、適宜置き換えて下さい。また、Rails的にはdevelopmentにしてあります。

もう1つ。SELinuxが悪さしてApacheが立ち上がらないので(というか必要なことなんでしょうが、開発ではいらないので)「/etc/selinux/config」を

SELINUX=disabled

とし、これは再起動後もdisabledにするものなので、コマンドラインでも

setenforce 0

として、現在も無効にします。そして

$ /etc/init.d/httpd restart

します。

最後にVirtualHostを使っているので、「/etc/hosts」の最初の行のローカルホストの部分に「www.comachi.com」を追記します。すなわち

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4 www.comachi.com

さてさてあとは、railsのアプリを作成するだけです。

comachiアプリを作成

とりあえず今回のアプリ名を「comachi」と名付け、まずは何はなくともrailsコマンドのnewを実行します(comachiを作成する親ディレクトリにcdしてから)。

cd /var/www/html/rails
$ rails new comachi -d postgresql -T

※-Tはunit testを作成しないオプションで、Rspec等を使う場合には必要ないので付けました。

DBはsqliteがデフォルトなので、「-d postgresql」を付けてPostgreSQLを利用するようにします。ここで「Javascriptライブラリがない云々」とか怒られます。「therbyracer」を入れないとダメで、後で必要になるかもしれない「execjs」も含めて、GemFileに以下の2行を書き込みます。

gem 'therubyracer', platforms: :ruby
gem 'execjs'

そして

bundle install

を実行します。

外見はTwitter Bootstrap

Twitter Bootstrapを使ってデザインするので、Gemfileに以下を書き込みます(と思ったですが、後にこれは利用するBootstrapが2で、それ以外にgemなどで3が簡単に使えるようにならないか探したのですが、結局良さげなのがないのと「stackoverflow」であっさり、「ダウンロードしてvendor/assetに置くだけなんだから、自分でやれ」みたいなのを見て、全くその通りと思い、この方法をやめます)。

gem "twitter-bootstrap-rails"
gem "less-rails"

なので、↑はless-railsだけ残して、まずはBootstrapのダウンロードに行って、最新版をダウンロードして、解凍すると、

dist/
    css/
        bootstrap-theme.css
        bootstrap-theme.min.css
        bootstrap.css
        bootstrap.min.css ※
    fonts/
        glyphicons-halflings-regular.eot
        glyphicons-halflings-regular.svg
        glyphicons-halflings-regular.ttf
        glyphicons-halflings-regular.woff
    js/
        bootstrap.js
        bootstrap.min.js ※

というようなファイルが出てくるので、↑の※のファイルを(必要に応じて、追加します)

vendor/
    assets:
        javascripts/
            bootstrap.min.js
        stylesheets/
            bootstrap.min.css

というように、コピーします(vendorでなくても、「app/assets」でも大丈夫です)。
そして「app/assets/stylesheets/application.css」に

*= require bootstrap.min

を書き加えます。

また「app/assets/javascripts/application.js」に

//= require bootstrap.min

を書き込みます。

これで終了wtwitter-bootstrap-railsは使わないので、

gem "less-rails"

そして、以下を実行。

bundle install
最後にconfig/database.ymlの編集

データベースのinitdb で、作成したユーザやデータベース名に併せて、今回はdevelopment環境なので以下のように書き換えます。

development:
  adapter: postgresql
  encoding: utf8
  database: comachi
  pool: 5
  username: chikkun
  password: hogehoge

これで、ブラウザで http://www.comachi.com で次のような画面が見られたらOKです。

ok

これで開発が始められそうですw

WordPressのプラグイン作成 第一回

WordPress のプラグイン作成 はじめの一歩

posted at 2014/01/12 by denpoya@コワーキングスペース町田

最初のお話はプラグインを作成するための準備段階として、

  1. WordPressに「これはプラグインですよ」と認めてもらい、
  2. プラグインを有効化したときに実行してもらいたいメソッドactivate()を作り、それをWordPressに実行してもらう

というところまでお話していきたいと思います。activate()は、関数として実行させても良いですし、クラスで定義されたメソッドであっても良いので、両方の場合を紹介します。

そして、次回のテーマとしては、このactivate()メソッドの役割として、プラグインに必要なデータベースのテーブルを作ってもらうことにします。

WordPressにプラグインとして認めてもらう

プラグインを配置するディレクトリとPHPファイルを作っていきます。

プラグインの名前

では、作りたい機能からプラグインの名前を決めましょう。すでにある他のプラグインと同じ名前にならないように気をつけます。ここでは、私たちの社名「株式会社 C & D」を使って、「CD Plugin」をプラグイン名とします。

プラグイン・ファイル

次は、選択したプラグイン名に由来する名前のPHPファイルを作ります。プラグインをインストールする人は複数のプラグインを利用することでしょう。そのような自分以外のプラグイン・ファイル名と同じファイル名を使うことはできません。また、プラグインのディレクトリ名も同様に重ならない唯一の名前にします。

wordpress
:
|-- wp-content
|   |-- plugins     <----- プラグインを配置するディレクトリ
|   |   |-- cd_plugin   <----- 私たちのプラグインのディレクトリ
|   |   |   |-- CDPlugin.php
|   |   |   |-- cd_plugin.php
|   |   |   |--images
|   |   |   |   `-- cd_image.jpg
|   |   |   |--css
|   |   |   |   `-- style.css
|   |   |   |--js
|   |   |   |   `-- jquery.js

その中にはプラグイン・ファイルだけでなくプラグインで利用するCSSファイル、JavaScriptファイルや画像ファイルなどを配置するディレクトリもあることでしょう。

標準プラグイン情報

WordPress にプラグインの存在を認識させ、プラグイン管理画面に表示させる「標準プラグイン情報」は、プラグインのメインになるPHPファイルの先頭に記述します。それを私たちのcd_plugin.phpとします。

<?php
/*
 Plugin Name: (プラグインの名前)
 Plugin URI: (プラグインの説明と更新を示すページの URI)
 Description: (プラグインの短い説明)
 Version: (プラグインのバージョン番号。例: 1.0)
 Author: (プラグイン作者の名前)
 Author URI: (プラグイン作者の URI)
 License: (ライセンス名の「スラッグ」 例: GPL2)
*/

先のディレクトリ構造にはPHPファイルを二つ置きました。cd_plugin.phpCDPlugin.phpです。これらのうちcd_plugin.phpの方に「標準プラグイン情報」を書いてみましょう。
CDPlugin.phpやその他のディレクトリなどはなくてかまいません。「標準プラグイン情報」を記述することで、WordPressにプラグインであることを認めてもらえます。

  • cd_plugin.php に「標準プラグイン情報」を下のように記述しました。
<?php
/*
 Plugin Name: CD plugin
 Plugin URI: http://www.nothing.pom/cd_plugin
 Description: WordPress 初めてのプラグイン
 Author: コワーキング・スペース町田「小町」
 Version: 0.1
 Author URI: http://www.nothing.pom
*/

そして、WordPressの管理者としてログインし管理者メニューの[プラグイン]をクリックすると

プラグインとして登録

このようにプラグインの仲間入りができます。

プラグインが有効化された時に実行させる関数を登録する

『▲▲をしたタイミングで、◎◎をしてもらう。』そのための仕組みをプラグインフックといいます。フックにはたくさんの種類が用意されているので適切なフックを探す必要があります。プラグイン API/アクションフック一覧 が役に立つかもしれません。

関数register_activation_hook($file, $function)

これは、プラグインを有効化したときに実行してもらいたい関数を登録する関数です。
引数は、次のように説明されています(上記リンク参照)。

引数
$file (string) (必須)
    wp-content/pluginsディレクトリにあるメインプラグインファイルへのパス。フルパスが有効です。
    初期値: なし
$function (callback) (必須)
    プラグインが有効化されたときに実行される関数。PHPにおける疑似的な型callbackとして許可されたものである必要があります。
    初期値: なし
使用例

phpファイル自身の中に実行したい関数を記述している、またはインクルードされている場合には

register_activation_hook( __FILE__, 'myplugin_activate' );

のように登録します。

ここで「__FILE__」は、自分自身のPHPファイルのパス名が定義されている定数です。

また、クラスを作りそのメソッドを実行させたい場合には、まずクラスファイルをインクルードしておき、newメソッドでインスタンスを作成します。そして実行させたいメソッドを次のように登録することができます。

require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . "クラスファイル.php";
$clazz = new クラス名();

register_activation_hook(__FILE__, array($clazz, "クラスのactivateメソッド名"));

では、私たちの例で具体的に作っていきましょう。

  • CDPlugin.phpに以下のコードを記述します。
<?php
class CDPlugin {
    function __construct() {
    }

    public function activate() {
        echo "初めてのプラグイン"; <-- これはやってはいけない。怒られます。
    }
}
  • cd_plugin.php にコードを追加します。

CDPlugin.phpを読み込ませて、インスタンスを作ります。

<?php
/*
 Plugin Name: CD plugin
 Plugin URI: http://www.nothing.pom/cd_plugin
 Description: WordPress 初めてのプラグイン
 Author: コワーキング・スペース町田「小町」
 Version: 0.1
 Author URI: http://www.nothing.pom
*/

require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . "CDPlugin.php";
$cdp = new CDPlugin();

register_activation_hook(__FILE__, array($cdp, "activate"));

array()を使ってクラスとそのメソッド名を指定することで、プラグインが有効化されたタイミングで"activate"メソッドを実行するフックが登録されます。

ただし、「有効化」をしてこれを実行させるとメッセージが出てWordPressに怒られます。

有効化をしたとき

今回はここまで。

次回は、activate()メソッドの中を編集していきます。目標は、プラグインに必要なデータベースのテーブルを作成することです。その際、機能のバージョンアップに伴うテーブルの変更にも気を配っていきたいと思います。

PHPの参照をめぐる冒険

PHPの参照をめぐる冒険

知久です。

少しばかりPHPから離れてしまっていることもあって(最後にやったPHP案件はまだ数年前だけど、その1つ前はバージョン3や4の時代・・・w)、つい先日Facebook上で「Copy on write」っていうのがあって、特にPHPでは参照渡しなど使わなくても大丈夫などと教えてもらったりしたのですが、その時「やはり技術者は日々勉強だな」と反省し、少しPHPの参照周りを調べてみました。今回はちょっとその辺の報告です(間違いのご指摘、歓迎します)。

とりあえず、結論は以下のようです(説明はその後しますw)。

php_reference

そして、この結論をもう少し掘り下げてみます。

そもそも関数の引数は?

関数の呼び出しの種類として、よく聞かれる言葉に

  • Call by value(値渡し)
  • Call by reference(参照渡し)
  • Call by sharing(共有渡し)→参照の値渡しとも言われています

というものがあります。この中で「Call by reference(参照渡し)」と「Call by sharing(共有渡し)」の違いが多少難しく、Google先生に聞いても「わかっていない人が多い」という文言が目立ちますので、ここは気合いの入れどころのようですw。

じゃ、「参照渡し」ってなんだ?あたりを読んで(中の引用のところ)、僕なりに解釈すると

渡された仮引数が「左辺値」として使われる場合、
言い換えれば、「左辺値」として扱われることを意図した関数の呼び出しは
「Call by reference(参照渡し)」であり、
意味がない場合「Call by value(値渡し)」である。

という感じでしょうか。

function func($value)//←仮引数
{
    $value = うんたらかんたら;
}

という使い方をしているのが、くどいですが、「Call by reference(参照渡し)」だと解釈できます。

ちなみに「実引数は呼んでいる側の引数」で「仮引数は関数側で受ける引数」です。

もっと具体的に見ていきましょう(「渡し」という言い方が流通してしまっているので「参照による呼び出し」のような「呼び出し」を使わず、「渡し」を使っていきます)。

値渡し

下のコードを見て下さい。

<?php
function func1($value)//←仮引数
{
    $value = $value * 2;
}

function func2($value)
{
    $data = $value * 2;
    return $data;
}

$num = 123;
func1($num);//←実引数
echo $num."\n";// => 123
$num = func2($num);
echo $num."\n";// => 246

function func1($value)//←仮引数
{
    $value = $value * 2;
}

$num = 123;
func1($num);//←実引数

で呼ばれ、実引数で渡した123を2倍にして仮引数を上書していますが、その上書きが、関数の外のecho文では活かされていません。123のまま出力されています。

もちろん、func2のようにreturn文で$numを書き換えれば、$numに反映はできますが、この場合、仮引数の$valueは右辺としての意味はありますが、左辺としての意味はありません!

とうわけで、これらの関数の呼び出しでは仮引数を左辺においても意味がなく、「Call by value(値渡し)」ということになります。

参照渡し

次は前のとほんのちょっとだけ違うコードです。単に仮引数の前に「&」が付いてるのが違うだけです。

<?php
function func(&$value)//←仮引数
{
    $value = $value * 2;
}

$num = 123;
func($num);
echo $num."\n";// => 246

今度の場合は仮引数を左辺にして「値を2倍に」した結果が$numに反映されています(246が出力されています)。つまり、実引数を仮引数に渡し、これを左辺に利用して意味がある使い方になり「Call by reference(参照渡し)」ということになります。

C++やPascalなども同様のことができるようですが、Javaではできません(「Javaではできないだと〜」と思った人は次の章を読みましょうw)。PHPではこの&を仮引数に付けることにより、参照渡しが実現できるということです。

ちなみにJavaは次の「Cal by sharing(共有渡し)」であり、PHPもオブジェクトを代入した時の場合などもそうです。JavascriptやPython、Rubyなども「共有渡し」だそうなので、ここは重要です。

共有渡し

ちょっと長目ですが、次のコードを見て下さい。まずはRefTestというクラスは2つのインスタンス変数があり、コンストラクタでこれらの変数を初期化するという、とっても単純なクラスを定義しています。

通常はインスタンス変数はpublicではなく、privateでクラスを作るべきですが(そしてgetter、setterを作る)、話を単純にするために外からでもアクセスできるpublic変数にしています。

<?php
// コンストラクタとpublicな変数が2つある単純なクラス
class RefTest
{
    public $value1;
    public $value2;

    function __construct($v1, $v2)
    {
        $this->value1 = $v1;
        $this->value2 = $v2;
    }
}

このクラスをnewしていくつかの関数にそのオブジェクトを渡して、試してみましょう。

まずはref1関数。

// $refのメンバ変数value1を10に変更
function ref1($ref)
{
    $ref->value1 = 10;
}

$obj = new RefTest(1, 2);
// コンスタラクタの引数がそのまま出力
echo $obj->value1 . "\n"; // => 1
echo $obj->value2 . "\n"; // => 2

// ref1メソッドでvalue1の方が10に変更される
ref1($obj);
echo $obj->value1 . "\n"; // => 10
echo $obj->value2 . "\n"; // => 2

$obj = new RefTest(1, 2);
ref1($obj);

で、RefTestクラスをnewし、ref1関数に$objを渡します。関数の中身は

function ref1($ref)
{
    $ref->value1 = 10;
}

なので、value1の方を10に変更され、

echo $obj->value1 . "\n"; // => 10
echo $obj->value2 . "\n"; // => 2

となります。これは「Cal by sharing(共有渡し)」で、「「Cal by reference(参照渡し)」ではありません。どうして参照渡しじゃないかがポイントなんですが、次のref2関数の場合と比べてみましょう。

// 新しいオブジェクトで仮引数を上書き
function ref2($ref)
{
    $ref = new RefTest(100, 200);
}
$obj = new RefTest(1, 2);

// 仮引数に新しいRefTestオブジェクトをnewして代入←しかし、出力は前のまま
ref2($obj);
echo $obj->value1 . "\n"; // => 1
echo $obj->value2 . "\n"; // => 2

このref2の場合、関数の中で仮引数を新しくnewしたRefTestオブジェクトで上書きしています。そして、その時のコンストラクタの引数が(100,200)なのですが、関数実行後のvalue1やvalue2の出力はそのままです。

何が違うかというと下のように、仮引数である$refのメンバー変数を変更しているということと(左)、仮引数自体を書き換えていることです

$refのメンバー変数を変更している 仮引数自体を書き換えている


$ref->value1 = 10;


$ref = new RefTest(100, 200);

つまりPHPでは、変数にオブジェクトが格納されているという条件下では、明示的に&を仮引数に付けずに普通に関数を呼び出した場合、オブジェクトのリファレンスが実引数からコピーされて仮引数に渡り、別々の変数が同じオブジェクトを見ているのですが、仮引数を新しいオブジェクトで関数内で書き換えると、別々のオブジェクトを指すようになります。図にすると下のような感じでしょうか(上の2つの図)。

ref2

これは、結局アドレスという値がコピーされて仮引数に渡されるので「参照の値渡し」とも呼ばれ、人によっては「値渡し」という人もいますが、オブジェクトのメンバ等にはアクセスして、変更等もできるので、若干「値渡し」とは挙動が違いますので、ここでは共有渡しと呼んでおきます。

参照渡しと比べておきましょう。

// 新しいオブジェクトで仮引数を上書き(ただし参照渡し!)
function ref3(&$ref)
{
    $ref = new RefTest(100, 200);
}
// 参照渡しの仮引数を新しいRefTestオブジェクトをnewして代入←出力が変わる
ref3($obj);
echo $obj->value1 . "\n"; // => 100
echo $obj->value2 . "\n";// => 200

繰り返しですが、「function ref3(&$ref)」のように「&」が付いているだけで、アドレスがコピーされるのではなく、実質同じ変数になります(仮引数が実引数のエイリアスになるイメージでしょうか?変数のアドレス自体が渡るという言い方をする人もいます)。したがって、仮引数を新しいオブジェクトで関数内で書き換えると、どちらの変数も同じ新しいオブジェクトを指すようになります。

「実質」と言っているのは、内部的にPHPがどのように処理しているかは深入りしないということですw(「php zval」というキーワードで検索すると良いかもしれません)。

「値渡し/参照渡し/共有渡し」のまとめ

ここでまとめておきます。PHPでは、次のようになっています。

  • オブジェクトを入れた変数以外は「値渡し」
  • オブジェクトを入れた変数は「共有渡し」
  • 関数の仮引数に&を入れた場合は「参照渡し」

ということになります。以下、3つで違うことを頭に入れる必要がありそうですねw

共有渡しでも、オブジェクトの中身(メンバ変数)を変えることが可能 共有渡しでは、仮引数自体を書き換えても意味がない(左辺になれない) 参照渡しだと、左辺になれる(つまり実引数の方も変更される)
function ref1($ref)
{
    $ref->value1 = 10;
}
function ref2($ref)
{
    $ref = new RefTest(100, 200);
}
function ref3(&$ref)
{
    $ref = new RefTest(100, 200);
}
Copy-On-Write

さて、話がさらに複雑になります。上記でまとめた1番の項目オブジェクトを入れた変数以外は「値渡し」ですが、その値の内容を変えずに、参照だけをするだけなら「共有渡し」的に渡すというのが「Copy-On-Write」の考え方です。例えば、次のような大きな配列に値を入れた場合、値渡しするとメモリーがもったいないよね(大きな配列が2つになるので)、という発想です。

次のようなコードを実行すると、①のところをコメントアウトしていると普通に実行できますが、コメントを取るとメモリー不足のエラーが起こります。

<?php
#メソッド1
# 100万個の配列の値をすべて2倍にする
function func($arr)
{
    $end = count($arr);
    for ($i = 0; $i < $end; $i++) {
        // $arr[$i] = $arr[$i] * 2; // ①
    }
    return $arr;
}
# ここからMainメソッド
$array = Array();
# 100万個の配列作成
for ($i = 0; $i < 1000000; $i++) {
    $array[$i] = $i;
}

$array = func($array);

つまり、私のMacBook Airでは、デフォルトを倍にしたphp.ini内の「memory_limit」が

memory_limit = 256M

で①が実行されなければ、エラーもなく実行できるけれど、①が実行されると

Allowed memory size of 268435456 bytes exhausted

というエラーが起こる、ということです。

どうしてかというと、

        $arr[$i] = $arr[$i] * 2; // ①

では、配列に入っている値を2倍にして再格納しているので、渡された配列の中身を変えています。つまり渡された変数が変更されない限り、共有渡し的にになるわけですが、今回の例では実際には変更しているので、

この

$array = func($array);

で「func」というメソッドが呼ばれた時配列自体がコピーされて、100万個の配列が2個でき、メモリー不足になったわけです。「Copy On Write」はまさに「書きこむ時にはコピーする」ということなわけです。

ただ、今回何度も出てきた

function func(array &$arr)

&$arrの「&」をつけることにより、参照渡しになり、配列が2つできないので(コピーされないので)、先ほどの条件でもメモリー不足にはなりません。

とはいえ、「Copy On Write」の登場により、今回の例のように渡した配列の中身を変更したりしない限り、「&」を付けなくても大丈夫ということになります

このことによって、あまり「参照渡し」を利用する機会がなくなってきている(あるいはむしろバグの温床にもなる)というようなことを言っている人もいて、概ね、私も賛成です。ただ積極的に参照渡しにしないと困るという状況以外では、利用しない方が良いと思うのですが、一方で、配列の中身を変更する場合はコピーされるということでもあり、メモリー不足になってエラーになってしまう場合もあり得るということも事実です。

そもそも100万個の配列を扱うこと自体ダメじゃん、ということは言えますし、512Mではエラーにならないので、そうすることも可能ですが、それでも状況によっては、やはり、どうしても色々な理由で—予算の関係でメモリーを増やせないとか、配列をいくつかに分解して処理すると遅くなってしまうなど—100万個の配列を扱いたいというようなケースも現実にはあり、その場合は参照渡しを検討すべきかな、と思うわけです。

おまけ(どっちが速い?)

最後に参照渡しと値渡しでどれくらい速度が違うんだろうかな、と実験してみました。これは、与えられた条件で違ってくるものなので、PHPの関数では、参照渡しよりもreturnしたほうが速い!?では、どでかい文字列を参照変数に代入するのはパフォーマンス的に遅いというようなことを言っています。が、このリンク先の例では、「参照渡し」するような積極的な意義はなさそうです(先ほど、メモリー不足のような状況が起こるケースでは、と言いましたが、ここの例では空文字の変数を渡しており、単にリターン文で書き換えればもともと良い、というようなケースに見えますw)。

<?php
// reference.php
function func(array &$arr)
{
  $end = count($arr);
  for ($i = 0; $i < $end; $i++) {
        $arr[$i] = $arr[$i] * 2;
    }
}

for ($i = 0; $i < 1000000; $i++) {
    $array[$i] = $i;
}

func($array);

というような参照を渡した場合の結果が下のよう。

$ php reference.php 

real    0m0.481s
user    0m0.381s
sys     0m0.098s

また、値渡しの場合の下のようなコードでは

// value.php
function func(array $arr)
{
  $end = count($arr);
  for ($i = 0; $i < $end; $i++) {
        $arr[$i] = $arr[$i] * 2;
    }
    return $arr;
}

for ($i = 0; $i < 1000000; $i++) {
    $array[$i] = $i;
}

$array = func($array);

実行結果は、少しだけ、参照を渡したときよりも遅いですね。

time php value.php 

real    0m0.567s
user    0m0.449s
sys     0m0.116s

関数の値渡しと参照渡しどちらが速い?でも、似たような結果になっていますw

今回はこれでおしまい。

『メールサーバを立てる』–まとめ–

せっかくメールサーバを構築したので、後々のためにブログに残しておくことにしました。

基本的な流れを追って、シリアスな(?)ところは省いたりしています。この設定でも十分実用的だとは思うのですが。

メールサーバを構築する

メールサーバの構成を決める

メールサーバをどのような形で守っていこうかを決めておきましょう。
特に、スパムメールの対処法とパスワード認証ですかね。全体的な構成を以下のようにします。

  • SpamAssassin(スパムアサシン)でスパムフィルタリング。
  • そのために、Procmailが必要。
  • IMAP / POP3 サーバは、Dovecot(ダヴコット)を使い、IMAPに設定します。
  • SMTPサーバは、Postfixです。
  • SMTPの認証は、saslauthdでプレインテキストまたはmd5ログイン認証とします。

必要なものをインストール

# yum install postfix dovecot spamassassin
# yum install cyrus-sasl cyrus-sasl-md5 cyrus-sasl-plain

※ Procmail はおそらくインストール済みと思います。

設定の大まかな流れ

dovecot 設定手順
  1. 各設定ファイルを編集する
    • /etc/dovecot/dovecot.conf
    • /etc/dovecot/conf.d/10-auth.conf
    • /etc/dovecot/conf.d/10-mail.conf
    • /etc/dovecot/conf.d/10-master.conf
    • /etc/dovecot/conf.d/10-ssl.conf
  2. サービス実行
    • systemctl start dovecot.service
  3. システム起動時にサービスを自動実行
    • systemctl enable dovecot.service
spamassasin 設定手順
  1. SpamAssassin用のユーザの作成
  2. SpamAssassin設定ファイル最新化スクリプトの作成
    • /etc/cron.daily/spamassassin-update
  3. SpamAssassin学習スクリプト作成
    • /etc/cron.hourly/spamassassin-learn
  4. procmail 設定ファイル作成
    • /etc/procmailrc
    • /etc/logrotate.d/procmail
  5. スパム専用メールボックス作成
    • 既存ユーザ用
    • 新規ユーザ用
postfix 設定手順
  1. Postfix 設定ファイル編集
    • /etc/postfix/main.cf
    • /etc/postfix/master.cf
  2. sendmail をやめて、postfix に切り替える

  3. postfix、saslauthd、spamassassinの実行およびシステム起動時の自動実行

設定の詳細

各設定ファイルの内容を中心に記録しておくことにします。

dovecot 設定

以前は一つのファイルで設定していたと思うのですが、設定内容ごとにファイルに分けられています。

/etc/dovecot/dovecot.conf
  # 24行: コメントを外し変更(複数記述できますが、そのポートが開きます)
  protocols = imap
  # 30行: IPv6を使わないときは、コメントを外し変更する
  listen = *
/etc/dovecot/conf.d/10-auth.conf
  # 10行: プレインテキスト認証の時には、コメントを外して変更する
  disable_plaintext_auth = no
  # 100行: 追加
  auth_mechanisms = plain login
/etc/dovecot/conf.d/10-mail.conf
  # 30行: コメントを外して追加
  mail_location = maildir:~/Maildir
/etc/dovecot/conf.d/10-master.conf
  # 96-98行: コメントを外して追加
  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
      mode = 0666
      user = postfix # 追加
      group = postfix # 追加
  }
/etc/dovecot/conf.d/10-ssl.conf
  # 8行: SSLを使わないので変更
  ssl = no
spamassasin 設定
SpamAssassin用のユーザの作成
 # groupadd spamd
 # useradd -g spamd -s /bin/false -d /var/log/spamassassin spamd
 # chown spamd:spamd /var/log/spamassassin
SpamAssassin設定ファイル最新化スクリプトの作成と実行
  • spamassassin-update を作成する。

    #!/bin/bash
    # SpamAssassin設定ファイル最新版ダウンロード
    cd /etc/mail/spamassassin
    wget -qN http://www.flcl.org/~yoh/user_prefs
    
    # 設定ファイル更新時のみSpamAssassin再起動
    diff user_prefs user_prefs.org > /dev/null 2>&1
    if [ $? -ne 0 ]; then
    cp user_prefs local.cf
    
    # スパム判断したメールを添付形式にしないように設定
    echo "report_safe 0" >> local.cf
    
    #ベイジアンフィルタを有効にする最低のham数(defaultは200)
    echo "bayes_min_ham_num 1" >> local.cf
    
    #ベイジアンフィルタを有効にする最低のspam数(defaultは200)
    echo "bayes_min_spam_num 1" >> local.cf
    
    # スパム判断したメールの件名に「***SPAM***」を付加するように設定
    echo "rewrite_header Subject ***SPAM***" >> local.cf
    
    # SpamAssassin再起動
    /etc/rc.d/init.d/spamassassin restart > /dev/null
    fi
    cp user_prefs user_prefs.org
  • SpamAssassin設定ファイルを最新にする。

    # chmod +x spamassassin-update
    # ./spamassassin-update
  • 毎日自動実行させる。

    # mv spamassassin-update /etc/cron.daily/
SpamAssassin学習スクリプト作成
  • spamassassin-learn

    #!/bin/bash
    PATH=/usr/sbin:/usr/bin:/bin
    
    for user in `ls /home/`
    do
    # 正常メール
    hammail=/home/$user/Maildir/cur
    
    # 正常メール学習
    if [ -d "$hammail" ]; then
        # 正常メールをSpamAssassinに学習させる
        su $user -s "/bin/bash" -c "sa-learn --ham $hammail | \
        logger -p mail.info -t 'sa-learn for $user'"
    fi
    
    # スパムメール
    spammail=/home/$user/Maildir/.Spam/cur
    
    # スパムメール学習
    if [ -d "$spammail" ]; then
       # スパムメールをSpamAssassinに学習させる
       su $user -s "/bin/bash" -c "sa-learn --spam $spammail | \
       logger -p mail.info -t 'sa-learn for $user'"
    
       # 受信後一ヶ月経過したスパムメールを削除
       tmpwatch -m 720 $spammail
    fi
    done
  • スクリプトを1時間ごとに実行

    # chmod +x spamassassin-learn
    # mv spamassassin-learn /etc/cron.hourly/
procmail 設定ファイル作成
  • /etc/procmailrc

    SHELL=/bin/bash
    PATH=/usr/bin:/bin
    DROPPRIVS=yes
    MAILDIR=$HOME/Maildir
    DEFAULT=$MAILDIR/
    SPAM=$MAILDIR/.Spam/
    LOGFILE=$MAILDIR/.procmail.log # ログ出力先
    #VERBOSE=ON # 詳細ログ出力
    
    # SpamAssassinによるスパムチェック
    :0fw
    |/usr/bin/spamc
    
    # SpamAssassinがスパム判定したメールはスパム専用メールボックスへ配送
    :0
    *^X-Spam-Flag: YES
    $SPAM
  • procmailログローテーション設定ファイル作成
    /etc/logrotate.d/procmail
    /home/*/Maildir/.procmail.log {
    missingok
    nocreate
    notifempty
    }
スパム専用メールボックス作成
  • 既存ユーザ用
    スパム専用メールボックス作成スクリプトを作成し実行する
    spamfolder-create

    #!/bin/bash
    
    for user in `ls /home`
    do
    id -u $user > /dev/null 2>&1
    if [ $? -eq 0 ] && [ ! -d /home/$user/Maildir/.Spam/new ]; then
        mkdir -p /home/$user/Maildir/.Spam/new
        mkdir -p /home/$user/Maildir/.Spam/cur
        mkdir -p /home/$user/Maildir/.Spam/tmp
        chmod -R 700 /home/$user/Maildir/.Spam
        chown -R $user. /home/$user/Maildir/.Spam
        echo $user
    fi
    done
  • 新規ユーザ用
    新規ユーザ追加時に自動でスパム専用メールボックス作成

    # mkdir -p /etc/skel/Maildir/.Spam/{new,cur,tmp}
    # chmod -R 700 /etc/skel/Maildir/.Spam
postfix 設定

全体をまとめるような感じで設定していきます。

Postfix 設定ファイル編集
  • /etc/postfix/main.cf
    コメント行が多いので、有効にした行と有効になっていた行を集めてみました。
    行頭がスペースで始まると、上の行の続きになります。コピーするときには注意してください。
queue_directory = /var/spool/postfix
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
data_directory = /var/lib/postfix
mail_owner = postfix
mydomain = 【メールサーバにするドメイン】# example.com
myorigin = $mydomain
inet_interfaces = all
inet_protocols = all
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
unknown_local_recipient_reject_code = 550
mynetworks_style = subnet
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
home_mailbox = Maildir/
mail_spool_directory = /var/mail
mailbox_command = /usr/bin/procmail
debug_peer_level = 2
debugger_command =
     PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
     ddd $daemon_directory/$process_name $process_id & sleep 5
sendmail_path = /usr/sbin/sendmail.postfix
newaliases_path = /usr/bin/newaliases.postfix
mailq_path = /usr/bin/mailq.postfix
newaliases_path = /usr/bin/newaliases.postfix
mailq_path = /usr/bin/mailq.postfix
setgid_group = postdrop
html_directory = no
manpage_directory = /usr/share/man
sample_directory = /usr/share/doc/postfix-2.10.2/samples
readme_directory = /usr/share/doc/postfix-2.10.2/README_FILES

 ##################################################
 # ここから下は追加
 #

smtpd_client_restrictions =
  permit_mynetworks,
  reject_unknown_client,
  permit

smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth

smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous

smtpd_recipient_restrictions =
  permit_mynetworks,
  permit_auth_destination,
  permit_sasl_authenticated,
  # reject_non_fqdn_recipient,
  # reject_unauth_destination,
  reject

mailbox_size_limit = 1000000000

message_size_limit = 20000000

smtpd_recipient_limit = 50
  • /etc/postfix/master.cf
    該当行のコメントを外して、編集します。

    smtp      inet  n       -       n       -       -       smtpd
      -o content_filter=spamassassin
    submission inet n       -       n       -       -       smtpd
      #  -o smtpd_tls_security_level=encrypt
      -o smtpd_sasl_auth_enable=yes
      -o smtpd_client_restrictions=permit_sasl_authenticated,reject

    最後に付け加えます。

    spamassassin unix - n n - - pipe flags=R user=spamd argv=/usr/bin/spamc
      -e /usr/sbin/sendmail
      -oi -f ${sender} ${recipient}
sendmail をやめて、postfix に切り替える
 # systemctl stop sendmail.service
 # systemctl disable sendmail.service
 # alternatives --config mta
postfix、saslauthd、spamassassinの実行およびシステム起動時の自動実行
 # systemctl start postfix.service
 # systemctl enable postfix.service
 # systemctl start saslauthd.service
 # systemctl enable saslauthd.service
 # systemctl start spamassassin.service
 # systemctl enable spamassassin.service

以上で終了です。