min
Minimam INter framework for PHP, (*1)
これは何?
PHP 5.5以上のための、Ajax Webアプリ開発のためのフレームワークです。
開発時の手間をミニマムにすることを目標に作られています。
* コーディングの手間を最小化:コードやテストを自動生成します。また開発者が書くコード量を最小化します。
* 学習の手間を最小化:既存の定番クラスライブラリやツールを組み合わせることで、新しいフレームワークやツールを覚える手間を減らします。
* 試行錯誤の最小化:テストツールやデバッグツールをフレームワークに組み込むことで、問題を把握しやすくします。
* AjaxなWebアプリを簡単に作れるように、JQueryとPHPの通信をシンプルかつ確実にできるメソッドを提供します.
* データベースのCRUDもすべてAjaxです。
* Google ChromeのPHP-Consoleと組み合わせることで、デバッグが面倒なAjaxアプリ作りが楽になります。
* デザインにはBootstrap 3を採用しています。Bootstrap 3用のテンプレートも簡単に適用できます。
* 独自の認証ライブラリを内蔵し、ログインフォームを安全・簡単に作れます。, (*2)
利用クラスライブラリ
minは以下のクラスライブラリを利用しています。, (*3)
- "smarty/smarty":テンプレートクラス
- "validator/livr":バリデータクラス
- "php-console/php-console":Google Chromeのconsoleに出力できるデバッグクラス
- "apache/log4php":ファイルに実行ログを記録するクラス
- "hybridauth/hybridauth":Twitter、FacebookなどによるOAuth認証クラス
- "verot/class.upload.php":アップロードされた画像をハンドリングするクラス
- "phpoffice/phpexcel":Excelワークシートを操作するクラス
- "nesbot/carbon":日付時間データ操作クラス
また開発時には以下を利用します。, (*4)
- "phpunit/phpunit":テストフレームワーク
- "phpunit/phpunit-selenium":Webブラウザ・テストフレームワーク
- "fzaninotto/faker":ダミーデータ生成クラス
推奨環境
- PHP 5.5+
- Composer
- OS:Mac OS X or Linux
- Webブラウザ:Google Chrome
- テキストエディタ:TextMate or Sublime Text
Windowsでの利用は検証されていません。, (*5)
インストール
1. composerによるインストール
$ composer create-project zubapita/min [プロジェクト名] -s dev, (*6)
2. ZIPをダウンロードしもしくは、リポリトジをclone
新しいminアプリ(プロジェクト)を作成する
$ ./makeNewApp.php -a testApp -r /Users/mydir/workspace, (*7)
make New Min Application., (*8)
make application : testApp.
parentPath=/Users/mydir/workspace, (*9)
Please run 'composer install' at new app root., (*10)
「/Users/mydir/workspace/testApp」が作成され、その下に必要なファイル一式がコピーされます。
アプリのディレクトリに移動して、Composerで必要なライブラリをインストールします。, (*11)
$ cd /Users/mydir/workspace/testApp
$ composer install, (*12)
アプリ作成手順
ヒント
bin以下にアプリの作成、利用のためのコマンド群があります。
パラメータを付けないで実行すると、使い方の説明が表示されます。, (*13)
$ cd bin
$ ./makeNewApp.php, (*14)
make New Min Application., (*15)
usage: ./makeNewApp.php
-a [app name]
-r [root dir name(option)] ex)/Users/user/workspace, (*16)
データベースのモデルを作成する
MinではデフォルトではPDOをオーバーライドした独自のクラスでDBを利用します。
(もちろん自分の好みのDBクラスをインストールして使っても構いません)
この独自のDBクラスを利用するためには、
* DBのクラス
* 各テーブルのクラス
* 各テーブルを操作するモデルクラス
が必要で、それぞれコマンドで自動で生成できます。, (*17)
データベースのクラスを作成する
MySQLのデータベース「test」のクラスを作成し、
* ユーザー名:testuser
* パスワード:gh67L*K0
の場合、, (*18)
$ ./makeDbClassFile.php -d test -s mysql -u testuser -p gh67L*K0, (*19)
クラスファイルが
* model/_def/db/test.php
に作成されます。, (*20)
テーブルのクラスを作成する
- データベース「test」のすべてのテーブルのクラスを作成する。
./makeTableClassFiles.php -d test, (*21)
クラスファイルが
* model/_def/db/test/
以下に作成されます。, (*22)
- データベース「test」のテーブル「users」のクラスを作成する。
./makeTableClassFiles.php -d test -t users, (*23)
クラスファイルが
* model/_def/db/test/users.php
に作成されます。, (*24)
テーブルのモデルを作成する
- データベース「test」のテーブル「users」を操作するモデルクラスを作成する。
$ ./makeModelClassFiles.php -d test -t users, (*25)
クラスファイルが
* model/test/UsersList.php
* model/test/UsersRecord.php
に作成されます。, (*26)
model/test/UsersList.php は、usersテーブルから一覧データを取得したり、検索結果を取得するためのクラスです。, (*27)
使用例:, (*28)
$UsersList = new UsersList();
$list = $UsersList->get();
var_dump($list);, (*29)
model/test/UsersRecord.php は、usersテーブルにデータを挿入、更新、削除するためのクラスです。, (*30)
使用例:, (*31)
$data['id'] = 1;
$data['name'] = "Tom";
$data['birthday'] = "2001-10-15";, (*32)
$UsersRecord = new UsersRecord();
$UsersRecord->set($data);, (*33)
$data = $UsersRecord->get("id=1");
var_dump($data);
$data['name'] = "Tom Cat";
$UsersRecord->set($data);, (*34)
クラスファイル model/_def/db/test/users.php にキーとなるカラムの定義があり、
デフォルトではidカラムがキーとなっています。idを変えずに他のカラムの値を変更してset()を実行すれば
行がupdateされます。idがテーブルに存在しない値なら行がinsertされます。, (*35)
バリデータを調整する
モデルを使ったデータの挿入がうまく行かない場合は、レコードモデル(model/test/UsersRecord.phpなど)のバリデータの設定を確認してください。
デフォルトでは、すべてのカラムに['required'](必須)が設定されています。, (*36)
バリデータの記述ルールは以下を参照してください。
https://github.com/koorchik/LIVR, (*37)
モデルのテスト
モデルクラスファイルを自動生成すると、同時にtest/modelディレクトリの下にphpunit用のテストファイルが作成されます。
booksテーブルの場合は、
* test/model/books/BooksListTest.php (BooksListクラスのテスト)
* test/model/books/BooksRecordTest.php (BooksRecordクラスのテスト)
の2つのファイルが作成されます。, (*38)
phpunitがインストール済みならば、以下の様にコマンドラインでテストが行えます(phpunitに実行パスを通しておく必要があります)。, (*39)
$ phpunit BooksListTest.php
$ phpunit BooksRecordTest.php, (*40)
ただし、BooksRecordTest.phpは、そのままのテストではエラーになります。
内部でテーブルに挿入するデータを設定する dataProvider() メソッドがあるので、適切な挿入用データを出力するように調整してください。, (*41)
Webのビューとコントローラーを作成する
前提:このWebアプリのURLをhttp://test.mysite.jp/だとします。, (*42)
なお、Webアプリとして動かすためには、etc/local_vh.conf を参考にVirtualHostを設定してください。
基本は、htdocsがDocumentRootになるようにして、etc/rewrite.conf をincludeすれば動くはずです。
また、var/compiledに apacheが書き込めるようにしてください。, (*43)
基本, (*44)
ビューとコントローラーを作成するには、 makeNewCtlAndView.php に -m でモデル名を、-p でページ名を指定します。, (*45)
テーブル内のデータ一覧表示や検索を目的としたビューとコントローラーを作成する, (*46)
$ ./makeCtlAndView.php -m UsersList -p users, (*47)
コントローラーのクラスファイルが
* controller/users/usersCtl.php
に作成されます。, (*48)
ビューのテンプレートが
* view/users/index.html
に作成されます。, (*49)
テンプレートはSmartyのルールで記述されています。
またテーブル表示のパーツが view/users/includes/ 内にあります。, (*50)
このページには
* http://test.mysite.jp/users/
でアクセスできます。, (*51)
テーブルへのデータ挿入や更新を目的としたビューとコントローラーを作成する, (*52)
$ ./makeCtlAndView.php -m UsersRecord -p users/record, (*53)
コントローラーのクラスファイルが
* controller/users/record/usersRecordCtl.php
に作成されます。, (*54)
ビューのテンプレートが
* view/users/record/index.html
* view/users/record/add.html
* view/users/record/edit.html
に作成されます。, (*55)
テンプレートはSmartyのルールで記述されています。
またフォーム表示のパーツが view/users/record/includes/ 内にあります。, (*56)
これらのページには
* http://test.mysite.jp/users/record/
* http://test.mysite.jp/users/record/add.html
* http://test.mysite.jp/users/record/edit.html
でアクセスできます。, (*57)
テーブルを使用しないビューとコントローラーを作成する, (*58)
$ ./makeCtlAndView.php -p about, (*59)
コントローラーのクラスファイルが
* controller/about/aboutCtl.php
に作成されます。, (*60)
ビューのテンプレートが
* view/about/index.html
に作成されます。, (*61)
テンプレートはSmartyのルールで記述されています。, (*62)
このページには
* http://test.mysite.jp/about/
でアクセスできます。, (*63)
コントローラーのテスト
コントローラーを作成すると、自動的にPHPunit-selenium用のテストファイルがtest/controllerディレクトリの下に作成されます。
* UsersCtlの場合:test/controllerディレクトリの下に作成されます/users/UsersCtlTest.php
* UsersRecordCtlの場合:test/controllerディレクトリの下に作成されます/users/UsersRecordCtlTest.php, (*64)
test/controller/usersに移動して, (*65)
$ phpunit UsersCtlTest.php, (*66)
でテストが行えます(phpunitに実行パスを通して、selenium-serverとchromedriverもインストールしておく必要があります)。
テストファイルの中身を書き替えてご利用ください。, (*67)
データベース操作
minは以下の特徴を持つ、独自のデータベース操作クラスを内蔵しています。
* PDOをベースにしているため、高速。
* テーブルの操作を記述すると、内部ではプレースホルダを使用したSQLを自動生成してPDOを使ってデータベースを操作する。SQLインジェクションに強くて安全。
* テーブル上のユニークキーを決めておけば、挿入と更新を自動判断。
* メソッドチェインによるスマートな記述法。, (*68)
テーブル操作の基本
前提:データベース「testDB」に以下の構造を持つテーブル「books」があるとします。
このbooksテーブルをインスタンス化して、テーブルの操作を行います。, (*69)
CREATE TABLE books
(
id
int(11) unsigned NOT NULL AUTO_INCREMENT,
title
varchar(50) DEFAULT NULL,
author
varchar(50) DEFAULT NULL,
isbn
varchar(14) DEFAULT NULL,
price
int(11) DEFAULT NULL,
releaseDate
datetime DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;, (*70)
テーブル・インスタンスの取得
$_ = $this;
$->DB = $->getDB('testDB');
$->Books = $->getTable($_->DB, 'books');, (*71)
$_->Booksにbooksテーブルのインスタンスが格納されます。, (*72)
テーブルへの行挿入
$_ = $this;
$bookData = [
'title' = 'ハムレット',
'author = 'シェークスピア',
'isbn' = '978-4102020036',
'price' = 497 ,
'releaseDate' = '1967/9/27' ,
];
$result = $_->Books->saveSet($bookData);, (*73)
テーブルからの行取得と更新
titleが'ハムレット'の行を取得します。, (*74)
$_ = $this;
$columns = [
'id',
'title',
'author',
'price',
];
$condition = ['title'=>'ハムレット'];
$bookData = $_->Books->select($columns)->find($condition)->fetch();, (*75)
$bookDataに結果行が格納されます。, (*76)
$bookData['author'] = 'ウイリアム・シェークスピア';
$result = $_->Books->saveSet($bookData);, (*77)
取得した行のauthorカラムが書き替えられます。
デフォルトではidカラムがユニークラカムとなっているため、行を更新するにはidカラムを取得しておく必要があります。
ユニークカラムの設定は、model/_def/(DB名)/(テーブル名).php内に記述されているので、必要に応じて書き替えられます。, (*78)
テーブルからすべての行を取得
$_ = $this;
$columns = [
'id',
'title',
'author',
'price',
];
$condition = [];
$bookDataRows = $_->Books->select($columns)->find($condition)->fetchAll();, (*79)
1行ずつ取得したいときは、getRows()を利用します。, (*80)
$rows = $_->Books->select($columns)->find($condition)->getRows();
while ($row = $rows->fetch(PDO::FETCH_ASSOC)) {
var_dump($row);
}, (*81)
getRows()はPDOstatementを返すので、以後はPDOのメソッドによる操作が可能になります。, (*82)
検索条件の記述
検索の条件(Where句の条件)は、find()のパラメータとして指定します。, (*83)
$bookDataRows = $_->Books->select($columns)->find($condition)->fetchAll();, (*84)
パラメータ$conditionに記述するのですが、以下の様に文字列や配列で記述します。, (*85)
- 文字列
>$condition = "title='ハムレット'";
$condition = "price>300";, (*86)
- 配列
>$condition = ['title'=>'ハムレット'];
$condition = ['title'=>['opr'=>'=', 'val'=>'ハムレット']];, (*87)
$condition = ['price'=>['opr'=>'>', 'val'=>300]];, (*88)
複数カラムのAND検索(OR検索はサポートしていません), (*89)
$condition = [
'author'=>['opr'=>'=', 'val'=>'シェークスピア'],
'price'=>['opr'=>'<', 'val'=>1000]
];, (*90)
BETWEENも使えます。, (*91)
$condition = ['price'=>['opr'=>'BETWEEN', 'MIN'=>300, 'MAX'=>1000]];, (*92)
1.の文字列は記述が簡単ですが、SQLが生成されるときに、プレースホルダが生成されずに条件がそのままWhere句に使われます。危険なので使わないほうがいいでしょう。, (*93)
limit、offset、order by
$_ = $this;
$columns = [
'id',
'title',
'author',
'price',
];
$->Books->select($columns);
$->Books->offset(0)->limit(10)->orderBy('price DESC');
$condition = [];
$_->Books->find($condition)->fetchAll();, (*94)
ORDER BYで複数のカラムを指定するときは、以下のように指定します。, (*95)
1.文字列, (*96)
$order = 'price DESC, title ASC';
$_->Books->orderBy($order);, (*97)
2.配列, (*98)
$order = ['price DESC', 'title ASC'];
$_->Books->orderBy($order);, (*99)
group by
$_ = $this;
$columns = [
'id',
'title',
'author',
'price',
];
$->Books->select($columns);
$->Books->->groupBy('author');
$condition = [];
$_->Books->find($condition)->fetchAll();, (*100)
GROUP BYで複数のカラムを指定するときは、以下のように指定します。, (*101)
1.文字列, (*102)
$group = 'author, price';
$_->Books->groupBy($group);, (*103)
2.配列, (*104)
$group = ['author', 'price'];
$_->Books->groupBy($group);, (*105)
join
$->Sales = $->getTable($->DB, 'sales');
$->Books->join($_->Sales)->on('books.isbn=sales.isbn');, (*106)
INNER JOINの場合は, (*107)
$->Books->innerJoin($->Sales)->on('books.isbn=sales.isbn');, (*108)
モデルによるデータベース操作
makeModelClass.phpを使うと、テーブルを操作するための2つのモデルクラスが生成されます。, (*109)
- DataListクラス
- DataRecordクラス
DataListクラス
テーブル内の一覧の取得や検索結果の取得を行うクラスです。
テーブル名がbooksなら、BooksListクラスが、BooksList.php内に生成されます。
コントローラーから使用するときは, (*110)
$BookList = new BookList();
と記述します。必要に応じてクラスファイルがautoloadされます。, (*111)
- get($conditions, $currentPage)
テーブルから一覧を取得し、配列で返します。取得結果がない場合は0を返します。
最大取得行数はデフォルトでは10になっています。, (*112)
@param (array|string) $conditions 文字列もしくは配列で検索条件を指定します。, (*113)
@param integer $currentPage テーブル内の全行数を最大取得行数で割った数字を指定します。
最大取得行数10のとき、11行〜20行を取得したいときは、$currentPage=2とします。, (*114)
- setMaxItemsInPage($maxitems)
最大取得行数を設定します。, (*115)
@param integer $maxitems 最大取得行数, (*116)
現在の最大取得行数を返します。, (*117)
DataRecordクラス
テーブルへの行単位の手刀、挿入、更新を行うためのクラスです。挿入、更新時はバリデートも行います。
テーブル名がbooksなら、BooksRecordクラスが、BooksRecord.php内に生成されます。
コントローラーから使用するときは, (*118)
$BookList = new BookList();
と記述します。必要に応じてクラスファイルがautoloadされます。, (*119)
テーブルから該当する行を取得して返します。, (*120)
@param (array|string) $conditions 文字列もしくは配列で検索条件を指定します。, (*121)
テーブルに行を保存します。ユニークキーが指定されていないか、指定されていてもテーブル内に存在しない場合は、新規に行を挿入します。ユニークキーがテーブルに存在する場合は、更新します。, (*122)
組み込み機能を利用する
ログインフォームのインストール
etc/template/set/auth 以下にユーザー認証とTwitterやFacebookによるOAuth認証を行うための一式があります。
利用するには、まずユーザー認証を保存するデータベースを用意し、bin/install.phpを実行します。, (*123)
データベース名が「mindb」、ユーザー名「mindb」、パスワードなしでmysqlで作った場合, (*124)
$ cd bin
$ ./makeDbClassFile.php -d mindb -u mindb -s mysql
$ cd etc/template/set/auth/bin
$ chomod +x install.php
$ ./install.php -d mindb, (*125)
これで必要なモデル、ビュー、コントローラーの一式が保存され、デフォルトのヘッダにある「ログイン」メニューが使えるようになります。
ただし、OAuth認証を使うには、TwitterやFacebookのAPIキーを取得し、model/_def/api/以下のTwitterApiKey.phpなどにAPIキーを設定する必要があります。, (*126)
画像のAjaxアップロードのインストール
etc/template/set/uploadImg 以下に画像のAjaxアップロードを行うための一式があります。
利用するには、bin/install.phpを実行すると、画像アップロードを行うコントローラとビューのセットがインストールされます(モデルは使用していません)。
デフォルトのページ名はuploadImgですが、-p ページ名 でページ名を指定できます。
URLをhttp://mydomain/imageupload/ にしたいときは、以下の様に指定します。, (*127)
$ cd etc/template/set/uploadImg/bin
$ chomod +x install.php
$ ./install.php -p imageupload, (*128)
アップロードされた画像は、var/images/uploaded 以下に保存されます。
画像の表示はとなります。uploadedの部分を書き替えたいときはコントローラ内で変更してください。
但し、/img/は使えません。画像のパスに/img/が含まれていると自動的にhtdocs以下の画像ファイルが表示対象になります。これはetc/rewrite.confに設定されています。
var/imagesは固定ですが、変更したい場合はlib/Dispatch.php内のsendImage()を書き換えてください。
表示可能な画像の拡張子はjpeg、JPEG、jpg、JPG、png、PNG、gif、GIFです。変更したい場合はlib/Dispatch.php内のsetPathAndAction()を書き換えてください。, (*129)
ディレクトリ構造
- bin
アプリの作成使用する各種バッチコマンドの置き場所です。また自分でバッチコマンドを作成するための雛形もあります。
- contoroler
Webアプリのコントローラーの置き場所です。コントローラーはエンドポイントであるindex.phpから起動されます。
- etc
各種設定ファイルの置き場所です。
- htdocs
ドキュメントルートです。画像ファイルやfavicon、robots.txtなど静的ファイルの置き場所です。
htdocs/index.php がWebアプリの起点(エンドポイント)となり、URLに応じて各コントローラーを起動します。
画像ファイルはhtdocs/img/* や htdocs//img/ の下に置きます。このルールはetc/rewrite.confで設定できます。
- lib
minの動作に必要なクラスライブラリが置かれています。
- model
データベースを操作するモデルクラスの置き場所です。データベース以外のapi操作やExcel操作もモデル化してここに置きます。
モデル内のデータベースの操作は独自のデータベース操作クラスにより、SQLを書かずにシンプルに操作を記述できます。
- test
テストファイルの置き場所です。
- var
一時ファイルの置き場所です。
var/compiledにビューのテンプレートがコンパイルされたphpが置かれます。var/compiledはapacheから書き込み可能に設定しておく必要があります。
- vendor
composerによってインストールされる各種クラスライブラリの置き場所です。
- view
HTMLファイルの置き場所です。
view/index.html がトップページのHTMLです。
HTMLはSmartyのテンプレートファイルになっています。Smartyはif〜else文による条件分岐やforeachなどのループ、変数の計算や代入などをサポートした高機能なテンプレートクラスです。
minでは、表示部分のプログラミングはSmartyとJQueryでほとんど行います。
セキュリティ対策
フレームワークレベルで以下のセキュリティ対策を施してあります。, (*130)
クロスサイトスクリプティング(XSS)対策
Samrtyのescape_htmlフィルタを各コントローラのコンストラクタにデフォルトで設定してあります。, (*131)
$_->view->escape_html = true;, (*132)
これによって、すべてのSmarty変数の出力がHTMLエスケープされます。
エスケープを無効にしたいときは、変数を出力する際にnofilterを指定します。, (*133)
{$変数名 nofilter}, (*134)
SQLインジェクション対策
内蔵データベース操作クラスは、PDOのプレースホルダを使用しています。
これによって、マルチステートメントの実行などが無効化されます。, (*135)
入力文字列のエスケープ
外部からの入力を受け取るために$_GETと$_POSTの代わりに、次の5つのメソッドが用意されています。
* getGETNumValue('変数名', [デフォルト値]) // 数値を$_GET['変数名']から取得。数値でないか存在しない場合はデフォルト値を返す。
* getGETStrValue('変数名', [デフォルト値]) // 文字列を$_GET['変数名']から取得。文字列はHTMLエスケープされる。存在しない場合はデフォルト値を返す。
* getPOSTNumValue('変数名', [デフォルト値]) // 数値を$_POST['変数名']から取得。数値でないか存在しない場合はデフォルト値を返す。
* getPOSTtrValue('変数名', [デフォルト値]) // 文字列を$_POST['変数名']から取得。文字列はHTMLエスケープされる。存在しない場合はデフォルト値を返す。
* getPostedData(boolean $raw=false) // $_POSTをまとめて配列で返す。$rawをtrueに指定しなければ、値はHTMLエスケープされる。また、本番運用以外(=開発時)はデバッグしやすくするため、$_POSTではなく、$_REQUESTを返す(=POSTだけでなく、GETでテストできる)。, (*136)
クロスサイトリクエストフォージェリ(CSRF)対策
デフォルトで生成されるAjaxによるPOSTは、毎回トークンを埋め込んで接続元を判定しています。
トークンの生成にはopenssl_random_pseudo_bytes()を使用しています。, (*137)
セッションハイジャック対策
内蔵ログイン認証クラスは、ログイン時にsession_regenerate_id()を実行してセッションIDを再生成しています。, (*138)
安全なパスワード保存
内蔵ログイン認証クラスは、パスワードのハッシュ化にpassword_hash()を使用しています。, (*139)
安全なファイルアップロード
内蔵のAjaxファイルアップロードは、アップロードされたファイルを公開ディレクトリに置きません。, (*140)
以下、更新予定, (*141)
Ajaxによるデータベース操作
Google Maps連動