###経緯
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のみです。他の4つのファイルuser*.phpはサンプルです。保存する際はutf8を指定してください。「********」の箇所はDB接続の設定に書き換えてください。
<?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
<?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;
*/
<!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>
<!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>
<!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
[URL]http://~/index.php/user/index
[URL]http://~/index.php/user/edit/7
###参考
参考にさせていただきました。
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