とりあえず本番でエラーが起きてたら開発MLへメールしたい。レガシーPHP案件にとりかかる時はいつもやるphp.iniでメール飛ばす設定をしようとしたら、CakePHPがエラーを捕まえているので飛ばない。

大抵のフレームワークではググるとMailを飛ばすLoggerが見つかるもんだが、CakePHP1.3用のが見つからなかったので書いときました。

<?php
/**
 * メールを送るロガー
 *
 * 使い方:
 * // app/config/bootstrup.php:
 * CakeLog::config('mailLog', array(
 *     'engine' => 'MailLog',
 *     'to'     => 'to@example.com',
 *     'from'   => 'from@example.com'
 * ));
 */
class MailLog
{
    private $to;
    private $from;

    /**
     * オプションを設定する
     *
     * @param  array $options メールのto、fromの設定
     * @return void
     */
    public function __construct($options = array())
    {
        if (isset($options['to'])) {
            $this->to = $options['to'];
        }

        if (isset($options['from'])) {
            $this->from = $options['from'];
        }
    }

    /**
     * ログをメールで送る(ログを書く)
     *
     * @param  string $type    エラーの種類
     * @param  string $message エラーの内容
     * @return void
     */
    public function write($type, $message)
    {
        $subject = "[{$type}] ".mb_substr($message, 0, 32);
        mb_send_mail($this->to, $subject, $message, "From: {$this->from}");
    }
}

Sending log by mail for CakePHP1.3 — Gist

PSR0, PSR1, PSR2にしたがってPHPのソースコードをフォーマットしてくれるphp-cs-fixerを使う。(全部のルールに対応してるわけじゃない)

% sudo curl http://cs.sensiolabs.org/get/php-cs-fixer.phar -o /usr/local/bin/php-cs-fixer
% sudo chmod a+x /usr/local/bin/php-cs-fixer
% sudo php-cs-fixer self-update
% php-cs-fixer fix app/models/foo.php

vimから使う

~/.vimrc:

Bundle 'stephpy/vim-php-cs-fixer'
let g:php_cs_fixer_path = "/usr/local/bin/php-cs-fixer"
nnoremap <silent><leader>pcd :call PhpCsFixerFixDirectory()<CR>
nnoremap <silent><leader>pcf :call PhpCsFixerFixFile()<CR>

これでテンション上げてレガシーPHPを直す。

ヘッドレスブラウザとか言ってらんない人用。

selenium

% sudo su
% mkdir /usr/lib/selenium/
% cd /usr/lib/selenium/
% wget http://selenium.googlecode.com/files/selenium-server-standalone-2.25.0.jar
% mkdir -p /var/log/selenium/
% chmod a+w /var/log/selenium/

seleniumのinit scriptを書く。

% sudo vi /etc/init.d/selenium
#!/bin/bash

case "${1:-''}" in
    'start')
        if test -f /tmp/selenium.pid
        then
            echo "Selenium is already running."
        else
            export DISPLAY=:99
            java -jar /usr/lib/selenium/selenium-server-standalone-2.25.0.jar > /var/log/selenium/selenium-output.log 2> /var/log/selenium/selenium-error.log & echo $! > /tmp/selenium.pid
            echo "Starting Selenium..."

            error=$?
            if test $error -gt 0
            then
                echo "${bon}Error $error! Couldn't start Selenium!${boff}"
            fi
        fi
    ;;
    'stop')
        if test -f /tmp/selenium.pid
        then
            echo "Stopping Selenium..."
            PID=`cat /tmp/selenium.pid`
            kill -3 $PID
            if kill -9 $PID ;
                then
                    sleep 2
                    test -f /tmp/selenium.pid && rm -f /tmp/selenium.pid
                else
                    echo "Selenium could not be stopped..."
                fi
        else
            echo "Selenium is not running."
        fi
        ;;
    'restart')
        if test -f /tmp/selenium.pid
        then
            kill -HUP `cat /tmp/selenium.pid`
            test -f /tmp/selenium.pid && rm -f /tmp/selenium.pid
            sleep 1
            export DISPLAY=:99
            java -jar /usr/lib/selenium/selenium-server-standalone-2.25.0.jar > /var/log/selenium/selenium-output.log 2> /var/log/selenium/selenium-error.log & echo $! > /tmp/selenium.pid
            echo "Reload Selenium..."
        else
            echo "Selenium isn't running..."
        fi
        ;;
    *)      # no parameter specified
        echo "Usage: $SELF start|stop|restart|reload|force-reload|status"
        exit 1
    ;;
esac
% sudo chmod 755 /etc/init.d/selenium
% sudo update-rc.d selenium defaults

xvfbとfirefox(iceweasel)

% apt-get install xvfb iceweasel

xvfbのinit scriptを書く。

% sudo vi /etc/init.d/xvfb
#!/bin/bash

XVFB=/usr/bin/Xvfb
# XVFBARGS=":99 -fbdir /var/run -ac"
XVFBARGS=":99 -nolisten tcp -fbdir /var/run"
PIDFILE=/var/run/xvfb.pid
case "$1" in
  start)
    echo -n "Starting virtual X frame buffer: Xvfb"
    start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile --background --exec $XVFB -- $XVFBARGS
    echo "."
    ;;
  stop)
    echo -n "Stopping virtual X frame buffer: Xvfb"
    start-stop-daemon --stop --quiet --pidfile $PIDFILE
    echo "."
    ;;
  restart)
    $0 stop
    $0 start
    ;;
  *)
        echo "Usage: /etc/init.d/xvfb {start|stop|restart}"
        exit 1
esac

exit 0
% sudo chmod a+x xvfb
% update-rc.d xvfb defaults

散財したくなったのでAppleTV買いました。

AirPlay目当てで買ったんですが、AirPlayはMacBook Air(Late 2011)以降の対応だそうです。(持ってるのはMacBook Air Mid 2010)

やっちまった・・・。

出す出す言ってなかなか出来なかった怖話iPhoneアプリが出来ました。

http://itunes.apple.com/jp/app/bu-hua/id564486792?ls=1&mt=8

App Store初申請なので最初は絶対リジェクトされると思ってたんですがあっさり通ってびっくり。

@jishihaさんに聞いた「iOS6対応で申請がいっぱいきてるからそのどさくさで簡単に通るとおもいますよ」というここを強い言葉を信じでえいやと提出してよかったです。

結局、PhoneCap -> Titanium -> PhoneGap -> Nativeというように紆余曲折を経ました。

星5のステマしておきました。

祝Github導入

お陰様でsvnからGithubに移行。気軽にブランチ作れてとっても快適。lingrやjenkinsとの連携もやっぱりスムーズ。@hrysdとはしゃぎました。

cakeplusプラグイン

i18n関連が面倒だと書いたらコメント欄でk1LoWさんが教えて下さったcakeplusプラグインを入れてみた。超改善された。あざーす!

レガシー認証

以前も書いたが、全コントローラー、全アクションに認証処理がベタ書きされてる。そこでその処理をLegacyAuthComponetという名前で分けた。

<?php
/**
 * 旧認証
 *
 * 古い認証機能をラップしたもの。
 * 管理者ログインしていない場合はログイン画面に
 * リダイレクトする
 *
 * 使い方:
 * class FooController extends AppController
 * {
 *     public $compornents = array('LegacyAuth');
 * }
 */
class LegacyAuthComponent
{
    public $name = 'LegacyAuth';
    public $controller;

    public function startup(&$controller)
    {
        $this->controller =& $controller;
        $this->adminLoginRequired();
    }

    public function adminLoginRequired()
    {
        if (isset($_SESSION['ses_login']['id'])) {
            $id = $_SESSION['ses_login']['id'];
        } else {
            $id = null;
        }

        if (!($id > 0)) {
            $this->controller->Session->setFlash(__('admin_login_required', true));
            $this->controller->redirect('/login/');
        }
    }
}
?>

プログラマーを求めて

一人だけでいいんだけどとにかくPHPプログラマーが見つからない。PHPの老舗、アシアルさんに相談したが、直近では空いている人がいないとのこと。しかし同じくPHP老舗であり、PHPCon2012でもニアミスしていたアリウープの柏岡様を紹介していただいた。

なんとこのブログの記事を読んでいたとのことで、ぶっちゃけたところからお話することができてとても話が速かった。しかしアリウープさんもお忙しいとのことでまだ決まるかどうかわからない状態。

それとは別で以前、ちょっと一緒にやっていくのは難しそうと書いた会社様より、「具体的に何が悪いのか、どういう人材を探しているのか説明して欲しい」との電話があり、上司の方とお話した。

どうも、こちらの話しているユニットテスト/自動テストと先方の話しているテストが違っているような気がする。

「よくわからないけど要はそういうツールがあるんですよね?だったらうちもできますよ。普通やりますよね、自動テスト」という感じ。

「でも先日見せてもらったコードにはユニットテスト無かったですよね?」(ついでにいうと使ってるライブラリはPEAR DBとSmartyだけでしたよね?)というようにちょっと話が噛み合わなかった。

僕らはそんなにすごいプログラマーを募集してるわけじゃないんです。ただ、プライベート(仕事外)でコードを書いてるPHPプログラマーだったら誰でも歓迎なんです。

しかしSIerから離れて随分経つので忘れていました。世の中にはプライベートでコードを書かないプログラマーが沢山いるという事実を・・・。

レガシーPHP改善日記

この日記が終わるかもだと・・・?

ハハハ、あんなメール、俺にとってはご褒美だ。

関連:レガシーPHP改善日記シリーズ

appディレクトリ以下から対象を抽出し、potファイルを生成する。

% cake i18n extract

potファイルをpoファイルにマージする。

% msgmerge -U app/locale/jpn/LC_MESSAGES/default.po app/locale/default.pot

poファイルやmsgmerge等のツールについてはgettextをググる。

お世話になっております。フィヨルドの駒形です。

下記大変申し訳ありませんでした。NDAも理解しております。 該当文章をブログから削除しました。

私がどのような意図で様に関するブログを書いたのか 簡単に説明しておきたいと思います。 (そもそもブログを書くのはとても工数のかかる作業なので 理由無く書きません)

  • プログラマーのリクルーティングのため
  • 関係者間の情報共有(を強制的に行う)ため
  • 改善案や情報を外部から募るため
  • 改善したあかつきに、非公式販促コンテンツとするため

私はAAAAやBBBBに限らず、殆どのシステムのアイデアや実装は 簡単に真似できるので殆ど価値が無いと思っています。 (2日で概要が出来るぐらいなので)

ASP等のシステムの場合、そこに集まるユーザーやデータ、そしてその システムを維持し、改善していける組織は今体験しているように 簡単に真似することができません。 (なので弊社のシステムもオープンソースにしています)

「FJORDのWebサービスはオープンソースです ≪ FJORD, LLC」
http://fjord.jp/love/534.html

最初の方のブログにも書きましたが、「ヒドイ状態」などと笑い話になりますが、 業界的には非常にありふれた状態で、誰しも大っぴらに言えないだけで 周りにそういうシステムが沢山あります。

表向きにはいい言葉だけを並べますが、実情は大抵のシステムがヒドイ 状態です。そこを隠さず、真正面から改善し、内情すら公開して自信を持って 提供できるようになった時、ものすごい信頼を得られるサービスになり、 売上が大幅に上がるのではないか、そしてそういう態度がそれを是とする 開発チームの構築につながると考えたためです。

とはいえ、そんなことは私が勝手に考えて勝手にやったことなので ご迷惑をお掛けしてしまって申し訳ございませんでした。

他にも問題ある点があればご指摘いただければと思います。

以上、宜しくお願い申しあげます。

--
----------------------------------------------------------------------------------
 Masaki Komagata

 ブログ: http://docs.komagata.org/
 FJORD, LLC: http://fjord.jp/
 CMS for Cloud: http://lokka.org/
 スマホで怖い話: http://kowabana.jp/
----------------------------------------------------------------------------------

関連:レガシーPHP改善日記シリーズ

(レガシー改善に関しては文章が長くなるのでいつもと違って、〜だ、〜である口調で書きます。)

bake

CakePHPははじめてなのでところどころハマったが基本的なCRUDは出来た。というかbakeで殆ど生成されるのだが。CakePHP1.3のbakeはデフォルトでページングとカラム毎のソート機能が入っている。

しかし、「データがたくさんある時ページ分けして欲しい」というタスクが幾つかRedmineにあったし、ソースをみても既存の部分はbakeを使った形跡は無い。俺が初bakeか。

i18n

bakeにともなってi18nも使った(bakeで生成されるソースにはi18n前提だし)。現状、poファイルが無い。俺が初i18nか。

% cake bake i18n extract

こんな感じでソースからpotファイルを生成する。app/vendorsにPHPExcelというプラグインがあって、それの処理に死ぬほどメモリと時間がかかる。app/vendorsを除外する方法は無いものか。

ソースを見ると上記にpotを既存poにマージする機能があるっぽいが、やり方がわからなかったのでとりあえず初回はmsginitした。

macではデフォルトでgettextが入っているが、% brew link gettextしないとパスが通らないところが若干罠だった。

既存ソースに何故か

__('')
msgid ""
msgstr ""

みたいな空文字を翻訳している箇所が5箇所ぐらいあってmsginitが出来ない。どういう理由でこういうコードがあるのか不明だが、とりあえず消した。

validateエラーメッセージのi18n

modelでvalidateエラーメッセージをi18nしようとしたがsyntax errorが出る。何故?と思ったがphpではrubyと違ってインスタンス変数の定義は定義であって評価じゃない。初期値は書けるが関数の実行はできない。

要は下記はOKだが、

class User {
    public $name = 'komagata';
}

下記は駄目ということだ。

class User {
    public $length = strlen('komagata');
}

rubyを始めた頃はattr_accessorを見て「クラス定義内も普通に式が評価されるのか、凄いなー!」なんて思ったはずだが、それが当たり前になりすぎてそうじゃない言語が沢山あることを忘れていた…。(array()はOKなのでarrayは関数じゃなくてリテラルなのかな?)

class RegularOrder extends AppModel {
    public $validate = array(
        'interval' => array(
            array(
                'rule'    => 'numeric',
                'message' => __('must be number', true)
            )
        )
    );
}

何故これがsyntax errorなんだー!・・・みたいな。

FormHelperへの疑問

CakePHP1.3のbakeが生成するコードはFormHelperを使っている。railsのscaffoldと比較して疑問点がいくつかあった。

bakeが生成するviewのコードは下記のようになっている。(分かりやすく色々端折ってます)

<?= $form->input('interval') ?>

でも実際は下記のようにモデル名も書かないと動かない。

<?= $form->input('RegularOrder.interval') ?>

formはmodelを知ってるんだから自動で付けてくれてもいい気がするが、PHP4もサポートするせいでモデル名がわからないのかもしれない。それだったらPHP4サポートを切ったCakePHP2系だったら改善されてるのかも。

レガシー改善パターン "Partial Proxy Pattern"

既存のレガシーの中にリーダブルコードを書いていく為のショーモナイテクニックに勝手に名前を付けていきたいと思います。

既存のテンプレートの中の共通化したい部分はこんな感じで書かれてる。

<body>                      
  <?php include('../views/head-menu.ctp');?>
    <h2 class="title_1">注文入力</h2>

ファイル名と置き場所が分かり辛い。include(!)ではなく、CakePHPのElementを使って欲しい。かといって既存部分をいじったら他で何が起こるか分かったもんじゃない。怖い。

app/views/elements/admin/menu.ctp:

<?= $this->element('../head-menu') ?>

そこでこんな感じで本来置きたい場所に一旦既存のテンプレを読み込むだけのProxy的なElementを作る。そうすると、新しく実装するviewはリーダブルでいられる。

app/views/layouts/admin.ctp:

<?= $html->doctype('xhtml-trans') ?> 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
  <head>
    <?= $html->charset() ?>
    <title><?= $title_for_layout ?></title>
    <?= $html->css('admin') ?>
    <?= $scripts_for_layout ?>
  </head>
  <body>
    <?= $this->element('admin/menu') ?>
    <div id="content">
      <?= $content_for_layout ?>
    </div>
  </body>
</html>

レガシー改善パターン "CSS Proxy Pattern"

同じく、既存のCSS(style.css)はゴチャゴチャなので新しく作るview用のCSSはリーダブルにしたい。そこでこう!

app/webroot/css/admin.css:

@import url('style.css');

/* ここに新しいviewで必要なcssを書いてく */

レガシーCSSとリーダブルCSSを切り分けることが出来る。

絶賛募集中

僕と一緒にレガシー改善をしてくださるプログラマーを募集しています。常駐・受託、開発会社・フリーランス、派遣・バイト問いませんので是非お願いします。というか助けて欲しい・・・。詳しくはこちら

関連:レガシーPHP改善日記シリーズ

日本語ファイル使ったことなかったけど、レガシー改善でsvnからgitへ移行したら日本語ファイル沢山。

Command Line Toolsで入ってるgitは若干古い。

% git version
git version 1.7.10.2 (Apple Git-33)

1.7.12から日本語ファイル名(UTF-8-MAC)が扱えるようになってるのでbrewで入れる。

% brew info git
git: stable 1.7.12.1, HEAD
% brew install git

実験的な機能という位置付けらしいのでデフォルトではオフになってる。オンにする。

% git config --local core.precomposeunicode true 

マシン全体でオンにしたい場合は下記。

% git config --global core.precomposeunicode true 

参照:git 1.7.12でUTF8-MAC問題が解決 « Butaman-kun Project