PHP できるかな -試験問題やってみたよ

BMediaNode さんのPHPプログラマの技量を知りたい時に書いてもらうスクリプト をやってみる。ちょうど転職活動中だし。
コードは日記末尾に。

久々の PHP で忘れてる事多々。ブランクによるロスを差し引くとまず手続き型で書いて 20min, その後無理矢理クラス化して 40min、色々触って 60min が来たところでタイムアップ。ん〜、無駄な事やってる。
この手のコードは、仕事だと最初にフレームワークやライブラリを揃え量産体制を整えてから書くので実際に記述が必要なのは

  1. フォーム定義ファイル
  2. テンプレート
  3. 遷移分岐やフォーム送信後の処理をちょこちょこっと

で設計が明確なら所要時間は 20 min といった所。

仕様については少し疑問で、業務で書くなら QF::ApplyFilter で全角数字を半角数字に自動置換、ハイフン除去後の入力値の長さを検査して郵便番号の桁数にマッチしない場合はエラーメッセージ、と変えたい。

また PHP プログラマの実力テストなら限られた時間でコードを書かせるのも良いが、もっと猶予を与えて一つのテーマを書かせてみるのも面白い。
PEARフレームワークの普及により非機能要求をどれだけ低コストかつ高品質に実現出来るかが最近の PHP アプリケーションの質を分けており、ここのコードと考え方を見ればその人がハンドルを取った場合の開発パフォーマンスはある程度割り出せると思えるのがその理由。
ごく単純化した例を挙げると例えば入力値のチェック、これを行っていない、つまり機能が水準に達していないコードは論外。また分離と再利用、集約が考慮されていないコードはそのまま開発コストの上昇や品質に対するボトルネックに繋がる。
それなりにコードや他社の開発体制を見てきたが、出来るチームは開発基盤を整備し作業の自動化やライブラリを通じて全体の品質と効率を押し上げ下っ端コーダの担当を本当にコーディングが必要なパートに集中させるそのハンドリングが例外なく上手い。
戦闘員が欲しいならともかく、もう少し上で使うならチェックして損はないポイントだ。

試験問題 回答コード

PHP
末尾 5 行のパラメータを書き換えれば動きます。HTML_QuickForm, HTML_Template_Flexy 必須。
掲載用に行数を詰めているので可読性は低下気味。要求項目はそのまま解決箇所のコメントに写しているので、コードを読もうという酔狂な人は手がかりにどうぞ。output メソッドが処理概要の中心です。
ん〜、あまりよいコードではないかも。

_setupForm();
	}
	
	function _setupForm() {
		$qf = new HTML_QuickForm();
		// 入力ページでは郵便番号(7桁)の入力欄と送信ボタンを表示 
		// 確認ページで戻るボタンが押された場合は「郵便番号欄の値を保持したまま」入力ページを再表示 
		$qf->addElement('text', 'zipcode', '郵便番号(7桁):', array('size'=>10));
		// 入力ページで送信ボタンを押した際、郵便番号欄の値を半角に変換してハイフンを除去 
		$qf->applyFilter('zipcode', 'formFilterZipcode');
		// 郵便番号欄の値が「空文字列」または「7文字の半角数字以外」の場合は「郵便番号欄の値を保持」したまま入力ページを再表示 
		// 入力ページを再表示する再、郵便番号欄の下に入力値に応じて「入力されていません」または「入力内容が正しくありません」と表示 
		$qf->addRule('zipcode', '入力されていません', 'required', null, 'client'); 
		$qf->addRule('zipcode', '入力内容が正しくありません', 'numeric', null, 'client');
		$this->qf = $qf;
	}
	
	/** フォーム遷移: 初回アクセス */
	function _setFormStatusDefault() {
		$this->qf->addElement('submit', 'confirm', '確認');
	}
	
	/** フォーム遷移: 内容確認 */
	function _setFormStatusConfirm() {
		$qf =& $this->qf;
		// 確認ページでは郵便番号、戻るボタン、次へボタンを表示 
		$qf->freeze();
		$qf->addElement('submit', 'submit', '次へ');
		$qf->addElement('submit', 'edit', '戻る'); 
	}
	
	/** フォーム遷移: 修正 */
	function _setFormStatusEdit() {
		return $this->_setFormStatusDefault();
	}
	
	/** ページ遷移: 送信受け付け */
	function _setPageStatusSubmitted() {
		// 確認ページで次へボタンが押された場合は結果表示ページに「入力された郵便番号はxxx-xxxxです」と表示 
		$zipcode = $this->qf->exportValue('zipcode');
		$result_msg = sprintf('入力された郵便番号は%d-%dです', substr($zipcode, 0, 3), substr($zipcode, 3, 4));
		$this->result_msg = $result_msg;
	}
	
	/** QF をテンプレート出力用にフリーズする */
	function _buildForm(&$page) {
		$renderer =& new HTML_QuickForm_Renderer_ObjectFlexy(&$page); 
		$this->qf->accept($renderer); 
		$this->form = $renderer->toObject(); 
	}
	
	/** 画面出力 */
	function output() {
		$qf =& $this->qf;		
		// 入力ページ、確認ページ、結果表示ページから成る
		if ( (!$qf->validate()) || !empty($_REQUEST['edit']) ) {
			// 入力ページ or [戻る] 押し下げ or バリデート失敗
			$this->_setFormStatusDefault();
		}
		elseif(empty($_REQUEST['submit'])) {
			// 確認ページ
			$this->_setFormStatusConfirm();
		}
		else {
			// 結果表示ページ
			$this->_setPageStatusSubmitted();
		}
		
		// 出力処理
		$master = new HTML_Template_Flexy(
			array('templateDir' => $this->templateDir, 'compileDir' => $this->compileDir)
		);
		$this->_buildForm($master);
		$master->compile($this->masterTemplate);
		$master->outputObject($this); 
	}
	
}

/** フォームエレメント zipcode 用フィルタ */	
function formFilterZipcode($var) 
{
	// 全角->半角に置換
	// $var = mb_convert_kana($var, 'a')
	// ハイフンを除去
	return str_replace('-', '', $var);
}

$page = new zipEntry();
$page->compileDir = dirname(__FILE__) .'/tmp';
$page->templateDir = dirname(__FILE__) .'/tmp';
// ページデザインは後で差し替えるのでスクリプトとは別ファイルにしておく 
$page->masterTemplate = 'zip.tpl.html';
$page->output();
?>

HTML テンプレート
一部省略。

<head>
{form.javascript:h}
</head>
<body>
	{if:result_msg}
		{result_msg}
	{else:}
		{form.outputHeader():h}
		{form.hidden:h}
		<div> 
			{form.zipcode.label} {form.zipcode.html:h} 
			<div flexy:if="form.errors.zipcode" style="color:red;">{form.errors.zipcode}</div>
		</div>
		<div> {form.submit.label} {form.submit.html:h} </div>
		<div> {form.confirm.label} {form.confirm.html:h} </div>
		<div> {form.edit.label} {form.edit.html:h} </div>
		</form>
	{end:}
</body>