Pachira Reference

updated at

Pachiraの紹介

Pachiraは、Sinatra風ルーティング機能を持ったPHP製のマイクロフレームワークです。シンプルさを追い求めて、必要のない機能は全て削って必要な機能もほとんど削ったようなフレームワークですが、学習コストは極めて少なく、小規模な開発には十分使えるかと思います。

download(github)

セットアップ方法

ファイル構成

Pachiraは、以下のファイル構成で成り立っています。

system/*
Pachiraの本体で、通常は触ることはありません。10分で目を通せる規模のプログラムです。
application/*
実際に開発していくプログラムを置くディレクトリです。こちらもMVCフレームワークなどを触ったことがあれば、すぐ理解できるかと思います。
public/*
Pachiraを読み込むindex.phpと.htaccess、そして公開する全てのリソースを配置します。(画像、CSS、JSなど)
config.php
システムに関する設定、アプリケーションに関する設定などを行うファイルです。

フレームワークの設置

Web上で公開するアプリケーションの場合は、public以外のディレクトリやファイルは非公開ディレクトリ(HTTPでアクセスできないディレクトリ)に配置してください。

そのままの構成で設置する場合

framework以下のファイル一式をそのまま適当な場所に設置し、Webサーバの設定でframework/public/をドキュメントルートに指定します。

ディレクトリ構成を変える場合

config.phpでシステムとアプリケーションのディレクトリを正しく指定し、index.phpからconfig.phpが正しく読み込めていれば、ディレクトリを別々に配置しても問題ありません。例えば、system/を共有しながらアプリケーションを複数用意することも可能です。

編集する必要がある(かもしれない)ファイル

index.php
config.phpのパスを正しい場所に変更します。
.htaccess
ドキュメントルートに合わせて、RewriteBaseを変更します。
config.php
HOME: アプリケーションのルートURLを指定します。(末尾にスラッシュが必要)
その他、同様にSYSTEM_DIRAPPLICATION_DIRを指定します。

サンプルアプリケーション

Simple CMSというサンプルを置いていますので、参考にしてください。

機能の説明

Pachiraは、以下の6つの機能と、それに対応するグローバル関数を覚えるだけですぐに利用できます。

Config

config.php

必要に応じて、ドキュメントルート、システムディレクトリ、アプリケーションディレクトリを変更できます。また、アプリケーション固有の設定も、必要があればここで行います。

          define("HOME", "/");
define("SYSTEM_DIR", dirname(__FILE__) . "/system/");
define("APPLICATION_DIR", dirname(__FILE__) . "/application/");
        

Bootstrap

{APP_DIR}/bootstrap.php

コンフィグとシステムファイルが全て読み込まれたあと、アプリケーションの初期化や設定、ルータの読み込みを行うファイルです。たとえば、データベースにアクセスするプラグインを利用する場合は、接続はここで行います。

          // 例
session_start();

initialize_orm(DB_HOST, DB_NAME, DB_USER, DB_PASS);

add_router("app");
add_router("api");
add_router("admin");
        

Router

{APP_DIR}/routes/*.php

組み込みの関数によって、GETおよびPOSTのルーティングができます。

          get("^/$", function(){
  view("index");
});

get("^/inquiry$", function(){
  view("inquiry/index");
});

post("^/inquiry$", function(){
  // inquiry();
  view("inquiry/complete");
});
        

正規表現にマッチした文字列は、コールバック関数の引数として渡されます。

          get("^/news/page/([\d+])$", function($page){
  view("news/list", array("news_list" => News::find($page)));
});

get("^/news/([\d+])$", function($id){
  $news = News::load($id);
  $news ? view("news/detail", array("news")) : not_found();
});
        

request()を使うと、全てのHTTPメソッドに対応するルーティングを定義できます。

          request("^/$", function(){
  echo "Hello, Pachira!";
});
        

コールバック関数内でpass()を呼ぶと、次のルーティングに飛ぶことができます。

          // 全ページで実行される
request("", function(){
  pass();
});

// admin/以下へのアクセス時、ログインしてない場合はログイン画面へ
request("^/admin/", function(){
  logged_in() ? pass() : redirect("login");
});
        
ルーティングの正規表現について

ルーティングの方法は、なるべくシンプル且つ表現力を持たせるために、URLにマッチする正規表現のみ対応しています。(通常の文字列/パラメータ/ワイルドカードなどには対応していません。) ここで指定する正規表現は、デリミタは必要ありません。(そのため、デリミタをエスケープする必要もありません。)

※RESTについて

View

{APP_DIR}/views/*.php

view()を使うと、Viewファイルが読み込まれます。Viewは普通のPHPファイルで、テンプレートエンジンに対応しているわけではありません。

          // views/index.php
view("index");
        
          // views/news/list.php
view("news/list");
        

第二引数には、View内で使いたい変数の配列を渡すことができます。

          view("news", array(
  "title" => "News Page",
  "news" => $news
));
        

capture_view()を使うと、Viewを文字列として取得することができます。

          $mail_body = capture_view("text/mail-body", $params)
        

view_var()を使うと、全てのView共通で使える変数を定義できます。

          request("", function(){
  view_var("current_user", User::current());
  pass();
});
        

Model

{APP_DIR}/models/*.php

Modelは、システム初期化時に自動で全て読み込まれます。 models/以下のディレクトリに置いたファイルの読み込みも、再帰的に行われます。 命名規則や制限はありませんが、1ファイル1クラスを想定しています。(が、ルールではありません。)

Plugin

{APP_DIR}/plugins/*.php

Pluginは、システム初期化時に自動で読み込まれますが、Modelのように再帰読み込みは行われません。 plugins/libs/以下に、サードパーティ製のライブラリなどを置き、plugins/直下にそれらを読み込むスクリプトを置くことを想定しています。

FAQ

データベースに接続したい場合は?

Pachiraは、データベース接続には対応していません。プラグインを開発するか、サードパーティのライブラリをご利用ください。

Idiormを使った例

Idiorm

まず、plugins/libs/以下にIdiorm本体を配置します。(Pluginでの解説の通り、このファイルは自動では読み込まれません。)
plugins/libs/idiorm.php

このライブラリを読み込むためのプラグインを配置します。(このファイルは自動で読み込まれます。)
plugins/idiorm.php

            <?php

require_once dirname(__FILE__) . "/libs/idiorm.php";

function setup_idiorm($host, $name, $user, $password){
  ORM::configure("mysql:host=".$host.";dbname=".$name);
  ORM::configure("username", $user);
  ORM::configure("password", $password);
  ORM::configure("driver_options", array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
  ORM::configure("error_mode", PDO::ERRMODE_WARNING);
  ORM::configure("logging", true);
}

function con($table){
  return ORM::for_table($table);
}

function transaction($fn){
  $db = ORM::get_db();
  try{
    $db->beginTransaction();
    $fn();
    $db->commit();
  }catch(Exception $e){
    $db->rollBack();
    throw $e;
  }
}

          

Config内で、データベースの接続情報を指定します。

            define("DB_HOST", "localhost");
define("DB_NAME", "database")
define("DB_USER", "user")
define("DB_PASS", "password")
          

Bootstrap内で、プラグインを利用してデータベースに接続します。

            setup_idiorm(DB_HOST, DB_NAME, DB_USER, DB_PASS);
          

あとは、アプリケーションのどこでも使用できるようになります。

関数を定義したい場合は?

基本的にはどこに書いても問題なく動くのですが、下記のどちらかを推奨しています。

Pluginにする

上記のデータベース接続のように、再利用性のあるものはPluginとしてまとめておけば問題ありません。

functions.phpを用意する

アプリケーション固有の関数などは、一つのファイルとしてまとめておいても構いません。関数に限らず、(Pachiraに存在しない)どのようなファイルやディレクトリでも問題なくフレームワークに追加することができます。

              <?php

require_once APPLICATION_DIR . "functions.php";

// ...

            
{APP_DIR}/bootstrap.php
              <?php

function foo(){
}

function bar(){
}
            
{APP_DIR}/functions.php

Modelの読み込み順でエラーが出る場合は?

継承やインターフェイスなどの指定でエラーが起こる場合は、単純にModelファイル内で読み込んでも構いません。

            <?php

require_once APPLICATION_DIR . "models/object.php"

class Object2 extends Object {
}
          
{APP_DIR}/models/object2.php

ルータを使いたくない場合は?

  • ターミナルなど、内部でPachiraを利用したい時
  • マイグレーションを作りたい時
  • 別のシステムにPachiraを組み込みたい時
  • HTTPからアクセスできるポイントを複数用意したい時

public/index.phpを参考に、システムだけを読み込めば、ルーティングの処理を無視することができます。

          <?php

require_once "../config.php";
require_once SYSTEM_DIR . "loader.php";

// 以下に処理を記述
        

RESTは?

Pachiraは、GETとPOSTに対応するルーティング関数しか用意していませんが、request()関数を使いルータ内で分岐させることで、全てのHTTPメソッドに対応することができます。

          request("^/foo$", function(){
  switch(Request::method()){
  case "GET":
    break;

  case "POST":
    break;

  case "PUT":
    break;

  case "DELETE":
    break;

  case "HEAD":
    break;

  default:
    not_found();
  }
});