PHPフレームワーク「OFFWORK 5.2」

###経緯
PHPでアプリケーションフレームワーク的なものができたので、公開します。
現在100行程度のindex.phpですが、磨けば、ミニマリスト、シンプリスト、プリミティビスト、学習者、など、一部の方のお役に立つかもしれないと、妄想しています。
「OFFWORK」と名付けました（語源：One File Frame Work）。

###動作概要
「～/index.php/{コントロール名}/{アクション名}/{パラメータ}」の形式のURLでアクセスします。./controllers/{コントロール名}.phpの中にあるclass {コントローラ名}のfunction {アクション名}が呼ばれ処理した後、./views/{コントロール名}\_{アクション名}.phpが呼ばれ画面を作成します。functionの戻り値配列に"controller"か"view"があれば、画面作成のPHPを変更します。functionの戻り値配列に"redirect"があれば、リダイレクトを行います。「\_」から始まるコントローラ名とアクション名は、URLから呼び出されません。

モデルのクラスは、class \_{テーブル名} extends model を推奨します。\_{テーブル名}.phpのファイルを作り、コントローラからrequire\_onceしても構いませんが、コントローラのファイルの中にモデルのコードを書くと（小さいプログラムの場合）見通しが良いかも知れません。複数形/単数形、キャメルケース/スネークケース、などの命名規則は定めていません。

###実装例
フルームワークの本体はindex,phpのみです。他の４つのファイルuser*.phpはサンプルです。保存する際はutf8を指定してください。「********」の箇所はDB接続の設定に書き換えてください。

```./index.php
<?php
    session_start();
    ini_set('error_reporting', E_ALL);
    ini_set('display_errors', '1');
    $DSN="mysql:host=********;dbname=********;charset=utf8mb4";
    $DB_USERNAME="********";
    $DB_PASSWORD="********";
    $SESSION_NAME="APP1";
    @include("./local.php");//load local setting
    //----------
    class Model{
        protected $db;
        public function __construct(){
            global $DSN,$DB_USERNAME,$DB_PASSWORD;
            try{
                $options = array(
                    PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_EMULATE_PREPARES=>false,
                    PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC,
                    PDO::MYSQL_ATTR_INIT_COMMAND=>"SET CHARACTER SET 'utf8'");
                $this->db=new PDO($DSN,$DB_USERNAME,$DB_PASSWORD,$options);
            }catch(PDDOException $e){$this->exit500($e->getMessage());}
        }
        function executeQuery($sql,$vars,$fetchable=false){

            try{
                $stmt=$this->db->prepare($sql);
                $result=$stmt->execute($vars);
                if($fetchable==true){
                    $data=$stmt->fetchall();

					// key<-id
                    if(isset($data[0]['id'])){
                    	$tmp=array();
	                    $flag=true;
                    	foreach($data as $ele){
                    		if(isset($tmp[$ele['id']])){
                    			$tmp[$ele['id']]=$tmp[$ele['id']]+1;
                    		}else{
                    			$tmp[$ele['id']]=1;
                    		}
                    		if($tmp[$ele['id']]>1){$flag=false;}
                    	}
                    	if($flag==true){
	                        $data=array_column($data, null, 'id');
	                    }
                    }

                    return array("data"=>array(get_class($this)=>$data),"return"=>$result,"count"=>$stmt->rowCount());
                }else{
                    return array("return"=>$result,"count"=>$stmt->rowCount());
                }
            }catch(PDDOException $e){$this->exit500($e->getMessage());}
        }
        function exit500($msg){
            header('Content-Type: text/plain; charset=UTF-8', true, 500);
            exit(h($msg));
        }
    }
    function h($s){
        return htmlspecialchars($s,ENT_QUOTES,'UTF-8');
    }
    function redirect($s){
        $tmp=$_SERVER["HTTP_ORIGIN"].$_SERVER["SCRIPT_NAME"].$s;
        header('Location: '.$tmp);
        exit;
    }
    //session----------
    function _login($value){
        global $SESSION_NAME;
        session_regenerate_id(true);
        $_SESSION[$SESSION_NAME]=$value;
    }
    function _logout(){
        global $SESSION_NAME;
        unset($_SESSION[$SESSION_NAME]);
        //session_destroy();
    }
    function _verify_login($exitto){
        global $SESSION_NAME;
        if(isset($_SESSION[$SESSION_NAME])){return;}
        redirect($exitto);
    }
    //paramaters----------
    $controller="";$action="";$para=array();$msg=array();$fname="";
    if (!empty($_SERVER['PATH_INFO'])) {
        $para=explode('/',$_SERVER['PATH_INFO']);
        $controller=array_shift($para);if($controller==""){$controller=array_shift($para);}
        $action=array_shift($para);
    }
    //action----------
    function getControllerFileName($controller){
		return "./controllers/".basename($controller).".php";
		//return "./".basename($controller).".php";
	}
    function getViewFileName($controller,$view){
		return "./views/".basename($controller)."_".basename($view).".php";
		//return "./".basename($controller)."_".basename($view).".php";
	}
	{
        //controller&action----------
        $fname=getControllerFileName($controller);
        if($controller==""||substr($controller,0,1)=="_"){
        	$msg[]="No controller specified.";
        }elseif(file_exists($fname)){
            include($fname);
            if(class_exists($controller)){
                $class = new $controller();
		        if($action==""||substr($action,0,1)=="_"){
		        	$msg[]="No action specified in the class [".h($controller)."] .";
		        }elseif(method_exists($class,$action)){
                    $d=$class->$action($para);
                    if(!is_array($d)){$d=array($d);}
                }else{$msg[]="method [".h($action)."()] not found in the class [".h($controller)."] .";}
            }else{$msg[]="class [".h($controller)."] not found.";}
        }else{$msg[]="file [".h($fname)."] not found.";}
        //redirect----------
        if(isset($d['redirect'])){redirect($d['redirect']);}
        //view----------
        if(isset($d["cntroller"])){$controller=$d["cntroller"];}
        $view=$action;if(isset($d["view"])){$view=$d["view"];}
        $fname=getViewFileName($controller,$view);
    }
    if($controller==""||$action==""||substr($controller,0,1)=="_"||substr($action,0,1)=="_"){
    }elseif(!file_exists($fname)){$msg[]="view file [".h($fname)."] not found.";}
    //drawing----------
    header('Content-Type: text/html; charset=utf-8');
    header('X-FRAME-OPTIONS: SAMEORIGIN');
    foreach($msg as $x){echo $x."<br>";}
    if(file_exists($fname)){include($fname);}
// PHP frame work "offwork 5.2" made by arigato2050
```
```./controllers/user.php
<?php
class user{
    function login(){
        $msg="";
        if(isset($_REQUEST["username"])&&isset($_REQUEST["password"])){
            $_user=new _user();
            $result=$_user->check($_REQUEST["username"],$_REQUEST["password"]);
            if(count($result['_user'])==1){
                _login(current($result['_user']));
                return array("redirect"=>"/user/index");
            }else{$msg="usernameまたはpasswordが異なります";}
        }
        $username="";if(isset($_REQUEST["username"])){$username=$_REQUEST["username"];}
        return array("data"=>array("_user"=>array("username"=>$username,"password"=>"")),"msg"=>$msg);
    }
    function logout(){
        _logout();
        return array("redirect"=>"/user/login");
    }
    function index(){
        _verify_login("/user/login");
        $_user=new _user();
        $result=$_user->getAll();
        return array("data"=>$result);
    }
    function add(){
        _verify_login("/user/login");
        return array("view"=>"edit","data"=>array('_user'=>array(0=>array("id"=>0,"username"=>"","password"=>""))));
    }
    function edit($para){
        _verify_login("/user/login");
        $_user=new _user();
        $result=$_user->get1($para[0]);
        return array("data"=>$result);
    }
    function save($para){
        _verify_login("/user/login");
        $_user=new _user();
        $result=$_user->set1($_REQUEST);
        return array("redirect"=>"/user/index","data"=>$result);
    }
    function del($para){
        _verify_login("/user/login");
        $_user=new _user();
        $result=$_user->del1($para[0]);
        return array("redirect"=>"/user/index","data"=>$result);
    }
}
class _user extends Model{
    function getAll(){
        $result=$this->executeQuery("SELECT * FROM user;",array(),true);
        return $result['data'];
    }
    function check($username,$password){
        $result=$this->executeQuery("SELECT * FROM user WHERE username=:username and password=:password;",array(":username"=>$username,":password"=>$password),true);
        return $result['data'];
    }
    function get1($id){
        $result=$this->executeQuery("SELECT * FROM user WHERE id=:id;",array(":id"=>$id),true);
        return $result['data'];
    }
    function del1($id){
        $result=$this->executeQuery("DELETE FROM user WHERE id=:id;",array(":id"=>$id));
        return $result['data'];
    }
    function set1($para){
        if($para['id']==0){
            $result=$this->executeQuery("INSERT INTO user (username,password,created) VALUES (:username,:password,:created);",array(":username"=>$para['username'],":password"=>$para['password'],":created"=>date("Y/m/d H:i:s")));
        }else{
            $result=$this->executeQuery("UPDATE user SET username=:username, password=:password, modified=:modified WHERE id=:id;",array(":id"=>$para['id'],":username"=>$para['username'],":password"=>$para['password'],":modified"=>date("Y/m/d H:i:s")));
        }
        return $result['data'];
    }
}
/*
サンプルのDBは以下です。`id`は主キーかつAUTO_INCREMENTです。ログイン用のusername/passwordを登録してからお使いください。
CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `username` text,
  `password` text,
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*/
```
```./views/user_login.php
<!DOCTYPE html>
<html>
<body>
<h1>login</h1>
<form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/login/'; ?>" method="post">
username:<input type="text" name="username" value="<?php echo h($d['data']['_user']['username']); ?>"><br>
password:<input type="password" name="password" value="<?php echo h($d['data']['_user']['password']); ?>"><br>
<input type="submit" value="送信">
</form>
<?php echo $d["msg"]; ?><br>
<?php if($d["msg"]>""){echo date("Y/m/d H:i:s")."<br>";} ?>
</body>
</html>
```
```./views/user_index.php
<!DOCTYPE html>
<html>
<body>
<h1>index</h1>
<table border=1>
<tr>
<th>id</th>
<th>username</th>
<th>password</th>
<th>created</th>
<th>modified</th>
</tr>
<?php
    foreach($d['data']['_user'] as $row  ){
        echo "<tr>";
?>
<td><a href="<?php echo $_SERVER['SCRIPT_NAME'].'/user/edit/'.h($row['id']); ?>"><?php echo h($row['id']); ?></a></td>
<td><?php echo h($row['username']); ?></td>
<td><?php echo h($row['password']); ?></td>
<td><?php echo h($row['created']); ?></td>
<td><?php echo h($row['modified']); ?></td>
<?php
        echo "<tr>";
    }
?>
</table>
<form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/add/'; ?>" method="post">
<input type="submit" value="追加">
</form>
<br>
<form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/logout/'; ?>" method="post">
<input type="submit" value="ログアウト">
</form>
<!--
<br>cookie:<br>
<?php var_dump($_COOKIE); ?>
<br>session:<br>
<?php var_dump($_SESSION); ?>
-->
</body>
</html>
```
```./views/user_edit.php
<!DOCTYPE html>
<html>
<body>
<h1>edit</h1>
id:<?php
    if(current($d['data']['_user'])['id']==0){
        echo "(new)";
    }else{
        echo h(current($d['data']['_user'])['id']);
    }
?>
<form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/save/'; ?>" method="post">
<input type="hidden" name="id" value="<?php echo h(current($d['data']['_user'])['id']); ?>">
username:<input type="text" name="username" value="<?php echo h(current($d['data']['_user'])['username']); ?>"><br>
password:<input type="text" name="password" value="<?php echo h(current($d['data']['_user'])['password']); ?>"><br>
<input type="submit" value="送信">
</form>
<form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/index/'; ?>" method="post">
<input type="submit" value="キャンセル">
</form>
<?php if(current($d['data']['_user'])['id']!=0){ ?>
    <br>
    <form action="<?php echo $_SERVER['SCRIPT_NAME'].'/user/del/'.h(current($d['data']['_user'])['id']); ?>" method="post">
    <input type="submit" value="削除">
    </form>
<?php } ?>
</body>
</html>
```

###実行例

[URL]http://～/index.php/user/login
<img width="388" alt="20190805login.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76471/1676da8d-cb46-8845-3a45-b211ac7179b7.png">

[URL]http://～/index.php/user/index
 <img width="442" alt="20190805index.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76471/bb590438-3fc5-d6c8-8b9d-66da43029b90.png">

[URL]http://～/index.php/user/edit/7
 <img width="269" alt="20190805edit.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76471/cf002750-2c0e-519c-4da0-8f4915c54da8.png">

###参考

参考にさせていただきました。
https://qiita.com/kahirokunn/items/175b82295ab683ffb624
http://choilog.com/katty0324/blog/6
https://qiita.com/mitsuru793/items/45b2452284e321c7a5a9
https://qiita.com/mpyw/items/b00b72c5c95aac573b71
https://qiita.com/7968/items/6f089fec8dde676abb5b
https://qiita.com/t_kawai/items/ed5e86c32966931c8e9b