preg_match

郵便番号:/^[0-9]{3}-[0-9]{4}$/
電話番後:/^[0-9]{2,4}-[0-9]{2,4}-[0-9]{3,4}$/
E-mailアドレス:|^[0-9a-z_./?-]+@([0-9a-z-]+\.)+[0-9a-z-]+$|

filter_var(), FILTER_VALIDATE_EMAIL

The filter_var() function filters a variable with the specified filter.
Returns the filtered data on success, or FALSE on failure.
The FILTER_VALIDATE_EMAIL filter validates an e-mail address.

<?php
$email = "john.doe@example.com";

if (!filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
  echo("$email is a valid email address");
} else {
  echo("$email is not a valid email address");
}
?>

phpでMySQLに接続する書き方

phpでMySQLに接続, sql文にてデータを挿入する書き方例
$stmt->executeにて実行しています。変数の$dbhはdbhandlerの略です。

  try {
    $dbh = new PDO('mysql:host='/DB_HOST.';dbname='.DB_NAME,DB_USER,DB_PASSWORD);
  } else (PDOException $e){
    echo $e->getMessage();
    exit;
  }

  $stmt = $dbh->prepare("select * from users where instagram_user_id=:user_id limit 1");
  $stmt->execute(array(":user_id"=>$json->user->id));
  $user = $stmt->fetch();

if (empty($user)){
    $stmt = $dbh->prepare("insert into users(instagram_user_id, instagram_user_name, instagram_profile_picture, instagram_access_token, created, modified) values (:user_id, :user_name, :profile_picture,  :access_token, now(), now());");
    $pramas = array(
      ":user_id" =>$json->user->id,
      ":user_name" =>$json->user->username,
      ":profile_picture" =>$json->user->profile_picture,
      ":access_token"=>$json->access_token
    );
    $stmt->execute($params);

データの取得

$stmt = $dbh->prepare("select * from users where id=:last_insert_id limit 1");
    $stmt->execute(array(":last_insert_id"=>$dbh->lastInsertId()));
    $user = $stmt->fetch();

ログイン機能の実装

下のキャプチャのように、controller, model, viewにファイルを分けて作成しています。サインインデータはMysqlに格納して、ログイン時に呼び出して、判別しています。また、PostとGetでトークンの正誤判定も行っています。
%e7%84%a1%e9%a1%8c

controller.php

<?php

namespace MyApp;

class Controller {

  private $_errors;
  private $_values;

  public function __construct(){
    if(!isset($_SESSION&#91;'token'&#93;)){
      $_SESSION&#91;'token'&#93; = bin2hex(openssl_random_pseudo_bytes(16));
    }
    $this->_errors = new \stdClass();
    $this->_values = new \stdClass();
  }

  protected function setValues($key, $value){
    $this->_values->$key = $value;
  }

  public function getValues(){
    return $this->_values;
  }


  protected function setErrors($key, $error){
    $this->_errors->$key = $error;
  }

  public function getErrors($key){
    return isset($this->_errors->$key) ?  $this->_errors->$key : '';
  }

  protected function hasError(){
    return !empty(get_object_vars($this->_errors));
  }

  protected function isLoggedIn() {
    // $_SESSION['me']
    return isset($_SESSION['me']) && !empty($_SESSION['me']);
  }

  public function me(){
    return $this->isLoggedIn() ? $_SESSION['me'] : null;
  }

}

投票システム (1日1回)

jqueryのclick functionでopacityを変更し、更にformのvalueにidを入れて、ボタンが押されたら、MySQLにIDを格納します。そして、Mysqlからsql文でデータを読み込んで、集計結果を表示させます。また、IPアドレス、ユーザーエージェント、データで、複数投票を制限しています。

-reference
bindValue:Binds a value to a corresponding named or question mark placeholder in the SQL statement that was used to prepare the statement.
SQL INSERT INTO Statement:INSERT INTO table_name VALUES (value1,value2,value3,…);
SQLデータ取得:SELECT col_name1, col_name2, … FROM db_name.tbl_name;
SQL GROUP BY Statement:The GROUP BY statement is used in conjunction with the aggregate functions to group the result-set by one or more columns.
user agent:is software (a software agent) that is acting on behalf of a user.

index.php

<?php

require_once(__DIR__ . '/config.php');
require_once(__DIR__ . '/Poll.php');

try {
  $poll = new \MyApp\Poll();
} catch (Exception $e){
  echo $e->getMessage();
  exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST'){
  $poll->post();
}

$err = $poll->getError();

?>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Poll</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="styles.css">
</head>
<body>
  <?php if (isset($err)) : ?>
  <div class="error"><?= h($err); ?></div>
<?php endif; ?>
  <h1>Which do you like best?</h1>
  <form action="" method="post">
    <div class="row">
      <div class="box" id="box_0" data-id="0"></div>
      <div class="box" id="box_1" data-id="1"></div>
      <div class="box" id="box_2" data-id="2"></div>
      <input type="hidden" id="answer" name="answer" value="">
      <input type="hidden" name="token" value="<?= h($_SESSION&#91;'token'&#93;); ?>">
    </div>
    <div id="btn">Vote and See Results</div>
  </form>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
	<script>
  $(function(){
    'use strict';

    $('.box').on('click', function(){
      $('.box').removeClass('selected');
      $(this).addClass('selected');
      $('#answer').val($(this).data('id'));
    });

    $('#btn').on('click', function(){
      if ($('#answer').val() === ''){
        alert('Choose one!');
      } else {
        $('form').submit();
      }
    });
    $('.error').fadeOut(3000);
  });
  </script>
</body>
</html>

Poll.php

<?php

namespace MyApp;

class Poll{
  private $_db;

  public function __construct(){
    $this->_connectDB();
    $this->_createToken();
  }

  private function _createToken(){
    if(!isset($_SESSION['token'])){
      $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16));
    }
  }

  private function _validateToken(){
    if(
      !isset($_SESSION['token']) ||
      !isset($_POST['token']) ||
      $_SESSION['token'] !== $_POST['token']
    ){
      throw new \Exception('invalid token!');
    }
  }

  public function post(){
    try {
      $this->_validateToken();
      $this->_validateAnswer();
      $this->_save();
      //redirect to result.php
      header('Location: http://' . $_SERVER['HTTP_HOST'] . '/result.php');
    } catch(\Exception $e){
      // set error
      $_SESSION['err'] = $e->getMessage();
      // ridirect to index.php
      header('Location: http://' . $_SERVER['HTTP_HOST']);
    }
    exit;
  }

  public function getResults(){
    $data = array_fill(0, 3, 0);

    $sql = "select answer, count(id) as c from answers group by answer";
    foreach ($this->_db->query($sql) as $row){
      $data[$row['answer']] = (int)$row['c'];
    }
    return $data;
  }

  public function getError(){
    $err = null;
    if (isset($_SESSION['err'])){
      $err = $_SESSION['err'];
      unset($_SESSION['err']);
    }
    return $err;
  }

  private function _validateAnswer(){
    // var_dump($_POST);
    // exit;
    if (
      !isset($_POST['answer']) ||
      !in_array($_POST['answer'], [0, 1, 2])
    ){
      throw new \Exception('invalid answer!');
    }
  }

  private function _save(){
    $sql = "insert into answers
          (answer, created, remote_addr, user_agent, answer_date)
          values (:answer, now(), :remote_addr, :user_agent, now())";
    $stmt = $this->_db->prepare($sql);
    $stmt->bindValue(':answer', (int)$_POST['answer'], \PDO::PARAM_INT);
    $stmt->bindValue(':remote_addr', $_SERVER['REMOTE_ADDR'], \PDO::PARAM_STR);
    $stmt->bindValue(':user_agent', $_SERVER['HTTP_USER_AGENT'], \PDO::PARAM_STR);

    try {
      $stmt->execute();
    } catch(\PDOException $e){
      throw new \Exception('No more vote for today!');
    }
    // exit;
  }

  private function _connectDB(){
    try {
      $this->_db = new \PDO(DSN, DB_USERNAME, DB_PASSWORD);
      $this->_db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
    } catch(\PDOException $e){
      throw new \Exception('Failed to connect DB');
    }
  }

}

result.php

<?php

require_once(__DIR__ . '/config.php');
require_once(__DIR__ . '/Poll.php');

try {
  $poll = new \MyApp\Poll();
} catch (Exception $e){
  echo $e->getMessage();
  exit;
}

$results = $poll->getResults();
// var_dump($results);
// exit;
// $results = [
//   0 => 12,
//   1 => 32,
//   2 => 44
// ];

?>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Poll Result</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="styles.css">
</head>
<body>
  <h1>Result ...</h1>
    <div class="row">
      <?php for($i = 0; $i < 3; $i++) : ?>
      <div class="box" id="box_<?= h($i); ?>"><?= h($results&#91;$i&#93;); ?></div>
    <?php endfor; ?>
    </div>
    <a href="/"><div id="btn">Go Back</div></a>
</body>
</html>

styles.css

body {
  font-size: 16px;
  font-family: Arial, sans-serif;
  text-align: center;
  margin: 0;
  padding: 0;
}

h1 {
  font-size: 22px;
  margin: 30px 0;
}

.row {
  margin-bottom: 15px;
}

.box {
  width: 150px;
  height: 150px;
  cursor: pointer;
  display: inline-block;
  opacity: 0.5;
  color: #fff;
  font-size: 48px;
  font-weight: bold;
  line-height: 150px;
}
.box + .box {
  margin-left: 10px;
}

#box_0 { background: url('photo_0.jpg'); }
#box_1 { background: url('photo_1.jpg'); }
#box_2 { background: url('photo_2.jpg'); }

.selected {
  opacity: 1.0;
}

#btn {
  display: inline-block;
  width: 150px;
  padding: 7px;
  font-size: 14px;
  cursor: pointer;
  border-radius: 5px;
  background: #00aaff;
  box-shadow: 0 4px 0 #0088cc;
  color: #fff;
}

#btn:hover{
  opacity: 0.8;
}

.error{
  background: orange;
  padding: 7px;
  color: #fff;
}

mySQL

create database poll_php;
grant all on poll_php.* to dbuser@localhost identified by 'xxxx';
use poll_php
create table answers (
	id int not null auto_increment primary key,
	answer int not null,
	created datetime,
	remote_addr varchar(15),
	user_agent varchar(255),
	answer_date date,
	unique unique_answer(remote_addr, user_agent, answer_date)
);

クイズアプリ

予め正しい答えを全て配列の0に入れておき、click functionで正しいか正誤判定しています。選択肢はシャッフルして表示し、最後に、正解率を表示させています。

reference
Autoloading Classes:The spl_autoload_register() function registers any number of autoloaders, enabling for classes and interfaces to be automatically loaded if they are currently not defined. By registering autoloaders, PHP is given a last chance to load the class or interface before it fails with an error.

index.php

<?php

require_once(__DIR__ . '/config.php');

$quiz = new MyApp\Quiz();

if(!$quiz->isFinished()) {
  $data = $quiz->getCurrentQuiz();
  shuffle($data['a']);
}

?>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Intractive Art</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="styles.css">
</head>
<body>
  <?php if ($quiz->isFinished()) : ?>
    <div id="container">
      <div id="result">
        your score ...
        <div><?= h($quiz->getScore()); ?>%</div>
      </div>
     <a href=""><div id="btn">Replay</div></a>
    </div>
  <?php $quiz->reset(); ?>
  <?php else : ?>
    <div id="container">
      <h1>Q. <?= h($data&#91;'q'&#93;); ?></h1>
      <ul>
        <?php foreach ($data&#91;'a'&#93; as $a) : ?>
        <li class="answer"><?= h($a); ?></li>
        <!-- <li class="answer">A1</li>
        <li class="answer">A2</li>
        <li class="answer">A3</li> -->
        <?php endforeach; ?>
      </ul>
        <div id="btn" class="disabled"><?= $quiz->isLast() ? 'Show Result' : 'Next Question'; ?></div>
        <input type="hidden" id="token" value="<?= h($_SESSION&#91;'token'&#93;); ?>">
      </div>
  	<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script src="quiz.js"></script>
  <?php endif; ?>
</body>
</html>

autoload.php

<?php

spl_autoload_register(function($class){
  $prefix = 'MyApp\\';

  if (strpos($class, $prefix) === 0){
    $className = substr($class, strlen($prefix));
    $classFilePath = __DIR__ . '/' . $className . '.php';

    if (file_exists($classFilePath)){
      require $classFilePath;
    } else {
        echo 'No such class' . $className;
        exit;
    }
  }

});
&#91;/php&#93;

_answer.php
&#91;php&#93;
<?php

require_once(__DIR__ . '/config.php');

$quiz = new MyApp\Quiz();

try{
  $correctAnswer = $quiz->checkAnswer();
} catch (Exception $e) {
  header($_SERVER['SERVER_PROTOCOL']. ' 403 Forbidden', true, 403);
  echo $e->getMessage();
  exit;
}

header('Content-Type: application/json; charset=UTF-8');
echo json_encode([
  'correct_answer' => $correctAnswer
]);

Token.php

<?php

require_once(__DIR__ . '/config.php');

$quiz = new MyApp\Quiz();

try{
  $correctAnswer = $quiz->checkAnswer();
} catch (Exception $e) {
  header($_SERVER['SERVER_PROTOCOL']. ' 403 Forbidden', true, 403);
  echo $e->getMessage();
  exit;
}

header('Content-Type: application/json; charset=UTF-8');
echo json_encode([
  'correct_answer' => $correctAnswer
]);

Quiz.php

<?php

 namespace MyApp;

 class Quiz{
   private $_quizSet = &#91;&#93;;

   public function __construct(){
     $this->_setup();
     Token::create();

     if(!isset($_SESSION['current_num'])){
       $this->_initSession();
     }
   }

   public function checkAnswer(){
     Token::validate('token');
     $correctAnswer = $this->_quizSet[$_SESSION['current_num']]['a'][0];
     if (!isset($_POST['answer'])){
       throw new \Exception('answer not set!');
     }
     if($correctAnswer === $_POST['answer']){
       $_SESSION['correct_count']++;
     }
     $_SESSION['current_num']++;
     return $correctAnswer;
   }

   public function isFinished(){
     return count($this->_quizSet) === $_SESSION['current_num'];
   }

   public function getScore() {
     return round($_SESSION['correct_count'] / count($this->_quizSet) * 100);
   }

   public function isLast(){
     return count($this->_quizSet) === $_SESSION['current_num'] + 1;
   }

   public function reset(){
     $this->_initSession();
    //  $_SESSION['current_num'] = 0;
    //  $_SESSION['correct_count'] = 0;
   }

   private function _initSession(){
     $_SESSION['current_num'] = 0;
     $_SESSION['correct_count'] = 0;
   }

   public function getCurrentQuiz() {
     return $this->_quizSet[$_SESSION['current_num']];
   }

   private function _setup(){
     $this->_quizSet[] = [
       'q' => '1543年は、ある物が日本に伝えられた年でもある。それは何か?',
       'a' => ['鉄砲', 'キリスト教', '貿易', '忍法']
     ];
     $this->_quizSet[] = [
       'q' => '海上交通の要地に作られた港町で有名なのはどこ?',
       'a' => ['堺', '伊丹', '京都', '江戸']
     ];
     $this->_quizSet[] = [
       'q' => '1639年に鎖国は完成する。この年から、来航を禁止された国はどこ?',
       'a' => ['ポルトガル', 'イギリス', '清', 'オランダ']
     ];
   }
 }

config.php

quiz.js

$(function() {
  'use strict';

  $('.answer').on('click', function() {
    var $selected = $(this);
    if ($selected.hasClass('correct') || $selected.hasClass('wrong')) {
      return;
    }
    $selected.addClass('selected');
    var answer = $selected.text();

    $.post('/_answer.php', {
      answer: answer,
      token: $('#token').val()
    }).done(function(res) {
      $('.answer').each(function() {
        if ($(this).text() === res.correct_answer) {
          $(this).addClass('correct');
        } else {
          $(this).addClass('wrong');
        }
      });
      // alert(res.correct_answer);
      if (answer === res.correct_answer) {
        // correct!
        $selected.text(answer + ' ... CORRECT!');
      } else {
        // wrong!
        $selected.text(answer + ' ... WRONG!');
      }
      $('#btn').removeClass('disabled');
    });
  });

  $('#btn').on('click', function() {
    if (!$(this).hasClass('disabled')) {
      location.reload();
    }
  });

});
body {
  font-size: 16px;
  font-family: Arial, sans-serif;
}
#container {
  width: 500px;
  margin: 15px auto;
}
h1, ul > li {
  border: 1px solid #ddd;
  border-radius: 5px;
}
h1 {
  padding: 10px;
  height: 50px;
  font-size: 18px;
  margin: 0 0 10px;
}
ul {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
}
ul > li {
  margin-bottom: 7px;
  padding: 7px 10px;
  cursor: pointer;
}
#btn {
  text-align: center;
  float: right;
  width: 100px;
  padding: 7px;
  color: #fff;
  border-radius: 5px;
  background: #00aaff;
  box-shadow: 0 4px 0 #0088cc;
  cursor: pointer;
  font-size: 14px;
}
#btn.disabled {
  opacity: 0.5;
}
.correct{
  color: limegreen;
  font-weight: bold;
}
.wrong {
  color: #ddd;
}
.selected {
  font-weight: bold;
}
#result {
  border: 1px solid #ddd;
  border-radius: 5px;
  margin-bottom: 15px;
  text-align: center;
  padding: 30px 0 50px;
}
#result > div {
  font-size: 64px;
  font-weight: bold;
  margin-top: 30px;
}

todo app

mysqlと連携したtodoアプリです。削除に際して、チェックボックスはajaxを使っています。

reference
openssl_random_pseudo_bytes:Generate a pseudo-random string of bytes

<?php

session_start();

require_once(__DIR__ . '/config.php');
require_once(__DIR__ . '/functions.php');
require_once(__DIR__ . '/Todo.php');

// get todos
$todoApp = new \MyApp\Todo();
$todos = $todoApp->getAll();

//var_dump($todos);
//exit;

// create table todos(
//   id int not null auto_increment primary key,
//   state tinyint(1) default 0,
//   title text
// );
//
// insert into todos (state, title) values
// (0, 'todo 0'),
// (0, 'todo 1'),
// (1, 'todo 2');

?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Todo app</title>
  <link rel="stylesheet" href="styles.css">
  <style>
  </style>
</head>
<body>
  <div id="container">
      <h1>Todos</h1>
      <form action="" id="new_todo_form">
        <input type="text" id="new_todo" placeholder="What needs to be done?">
      </form>
      <ul id="todos">
        <?php foreach ($todos as $todo) : ?>
          <li id="todo_<?= h($todo->id); ?>" data-id="<?= h($todo->id); ?>">
            <input type="checkbox" class="update_todo" <?php if ($todo->state === '1') { echo 'checked'; } ?>>
            <span class="todo_title <?php if ($todo->state === '1') { echo 'done'; } ?>"><?= h($todo->title); ?></span>
            <div class="delete_todo">x</div>
          </li>
        <?php endforeach; ?>
        <li id="todo_template" data-id="">
          <input type="checkbox" class="update_todo">
          <span class="todo_title"></span>
          <div class="delete_todo">x</div>
        </li>
      </ul>
  </div>
  <input type="hidden" id="token" value="<?= h($_SESSION&#91;'token'&#93;); ?>">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
  <script src="todo.js"></script>
</body>
</html>
$(function() {
  'use strict';

   $('#new_todo').focus();
  // update
  $('#todos').on('click', '.update_todo', function() {
    // idを取得
    var id = $(this).parents('li').data('id');
    // ajax処理
    $.post('_ajax.php', {
      id: id,
      mode: 'update',
      token: $('#token').val()
    }, function(res) {
      if (res.state === '1') {
        $('#todo_' + id).find('.todo_title').addClass('done');
      } else {
        $('#todo_' + id).find('.todo_title').removeClass('done');
      }
    })
  });

  // delete
  $('#todos').on('click', '.delete_todo', function() {
    // idを取得
    var id = $(this).parents('li').data('id');
    // ajax処理
    if (confirm('are you sure?')){
      $.post('_ajax.php', {
        id: id,
        mode: 'delete',
        token: $('#token').val()
      }, function() {
        $('#todo_' + id).fadeOut(800);
      });
    }
  });

  // create
  $('#new_todo_form').on('submit', function() {
    // titleを取得
    var title = $('#new_todo').val();
    // ajax処理
    $.post('_ajax.php', {
      title: title,
      mode: 'create',
      token: $('#token').val()
    }, function(res) {
      // liを追加
      var $li = $('#todo_template').clone();
      $li
        .attr('id', 'todo_' + res.id)
        .data('id', res.id)
        .find('.todo_title').text(title);
      $('#todos').prepend($li.fadeIn());
      $('#new_todo').val('').focus();
    });
    return false;
  });

});
<?php

session_start();

require_once(__DIR__ . '/config.php');
require_once(__DIR__ . '/functions.php');
require_once(__DIR__ . '/Todo.php');

$todoApp = new \MyApp\Todo();

if ($_SERVER&#91;'REQUEST_METHOD'&#93; === 'POST') {
  try {
    $res = $todoApp->post();
    header('Content-Type: application/json');
    echo json_encode($res);
    exit;
  } catch (Exception $e) {
    header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
    echo $e->getMessage();
    exit;
  }
}
<?php




namespace MyApp;

class Todo {
  private $_db;

  public function __construct() {

    $this->_createToken();

    try {
      $this->_db = new \PDO(DSN, DB_USERNAME, DB_PASSWORD);
      $this->_db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
    } catch (\PDOException $e) {
      echo $e->getMessage();
      exit;
    }
  }

  private function _createToken(){
    if (!isset($_SESSION['token'])){
      $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16));
    }
  }

  public function getAll() {
    $stmt = $this->_db->query("select * from todos order by id desc");
    return $stmt->fetchAll(\PDO::FETCH_OBJ);
  }

  public function post() {
    $this->_validateToken();
    if (!isset($_POST['mode'])) {
      throw new \Exception('mode not set!');
    }

    switch ($_POST['mode']) {
      case 'update':
        return $this->_update();
      case 'create':
        return $this->_create();
      case 'delete':
        return $this->_delete();
    }
  }

  private function _validateToken(){
    if (
      !isset($_SESSION['token']) ||
      !isset($_POST['token']) ||
      $_SESSION['token'] !== $_POST['token']
    ) {
      throw new \Exception('invalid token!');
    }
  }
  private function _update() {
    if (!isset($_POST['id'])) {
      throw new \Exception('[update] id not set!');
    }

    $this->_db->beginTransaction();

    $sql = sprintf("update todos set state = (state + 1) %% 2 where id = %d", $_POST['id']);
    $stmt = $this->_db->prepare($sql);
    $stmt->execute();

    $sql = sprintf("select state from todos where id = %d", $_POST['id']);
    $stmt = $this->_db->query($sql);
    $state = $stmt->fetchColumn();

    $this->_db->commit();

    return [
      'state' => $state
    ];

  }

  private function _create() {
    if (!isset($_POST['title']) || $_POST['title'] === '') {
      throw new \Exception('[create] title not set!');
    }

    $sql = "insert into todos (title) values (:title)";
    $stmt = $this->_db->prepare($sql);
    $stmt->execute([':title' => $_POST['title']]);

    return [
      'id' => $this->_db->lastInsertId()
    ];
  }

  private function _delete() {
    if (!isset($_POST['id'])) {
      throw new \Exception('[delete] id not set!');
    }

    $sql = sprintf("delete from todos where id = %d", $_POST['id']);
    $stmt = $this->_db->prepare($sql);
    $stmt->execute();

    return [];

  }
}

PHP カレンダー


カレンダーにはテーブルの中に、phpのdate関数フォーマットを使います。wはNumeric representation of the day of the weekです。
PHP Date:http://php.net/manual/en/function.date.php

-reference
DatePeriod class:A date period allows iteration over a set of dates and times, recurring at regular intervals, over a given period.
sprintf:Return a formatted string
var_dump: Dumps information about a variable

<?php

require 'Calendar.php';

function h($s){
  return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

$cal = new \MyApp\Calendar();

?>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Intractive Art</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="styles.css">
	<style>
	</style>
</head>
<body>
  <table>
    <thead>
        <tr>
            <th><a href="/?t=<?php echo h($cal->prev); ?>">&laquo;</a></th>
            <th colspan="5"><a href=""><?php echo h($cal->yearMonth); ?></a></th>
            <th><a href="/?t=<?php echo h($cal->next); ?>">&raquo;</a></th>
        </tr>
    </thead>
    <tbody>
      <tr>
          <td>Sun</td>
          <td>Mon</td>
          <td>Tue</td>
          <td>Wed</td>
          <td>Thu</td>
          <td>Fri</td>
          <td>Sat</td>
      </tr>
      <?php $cal->show(); ?>
    </tbody>
    <tfoot>
      <tr>
          <th colspan="7"><a href="/">Today</a></th>
      </tr>
    </tfoot>
  </table>
	<script>
	</script>
</body>
</html>
<?php

namespace MyApp;

class Calendar {
  public $prev;
  public $next;
  public $yearMonth;
  private $_thisMonth;

  public function __construct(){
    try {
      if(!isset($_GET&#91;'t'&#93;) || !preg_match('/\A\d{4}-\d{2}\z/', $_GET&#91;'t'&#93;)){
        throw new \Exception();
      }
      $this->_thisMonth = new \DateTime($_GET['t']);
    } catch (\Exception $e){
      $this->_thisMonth = new \DateTime('first day of this month');
    }
    $this->prev = $this->_createPrevLink();
    $this->next = $this->_createNextLink();
    $this->yearMonth = $this->_thisMonth->format('F Y');
  }

  private function _createPrevLink(){
    $dt = clone $this->_thisMonth;
    return $dt->modify('-1 month')->format('Y-m');
  }

  private function _createNextLink(){
    $dt = clone $this->_thisMonth;
    return $dt->modify('+1 month')->format('Y-m');
  }

  public function show() {
    $tail = $this->_getTail();
    $body = $this->_getBody();
    $head = $this->_getHead();
    $html = '<tr>' . $tail . $body . $head . '</tr>';
    echo $html;
  }
   private function _getTail(){
     $tail = '';
     $lastDayOfPrevMonth = new \DateTime('last day of ' . $this->yearMonth . ' -1 month');
     while($lastDayOfPrevMonth->format('w') < 6){
       $tail = sprintf('<td class="gray">%d</td>', $lastDayOfPrevMonth->format('d')) .
       $tail;
       $lastDayOfPrevMonth->sub(new \DateInterval('P1D'));
     }
     return $tail;
   }

   private function _getBody(){
     $body = '';
     $period = new \DatePeriod(
       new \DateTime('first day of' . $this->yearMonth),
       new \DateInterval('P1D'),
       new \DateTime('first day of ' . $this->yearMonth . ' +1 month')
     );
     $today = new \DateTime('today');
     foreach($period as $day){
       if ($day->format('w') === '0') { $body .= '</tr><tr>';}
       $todayClass = ($day->format('Y-m-d') === $today->format('Y-m-d')) ? 'today' : '';
       $body .= sprintf('<td class="youbi_%d %s">%d</td>', $day->format('w'),
       $todayClass, $day->format('d'));
     }
     return $body;
   }
   private function _getHead() {
     $head = '';
     $firstDayOfNextMonth = new \DateTime('first day of ' . $this->yearMonth . ' +1 month');
     while ($firstDayOfNextMonth->format('w') > 0) {
       $head .= sprintf('<td class="gray">%d</td>', $firstDayOfNextMonth->format('d'));
       $firstDayOfNextMonth->add(new \DateInterval('P1D'));
     }
     return $head;
   }
}






// $yearMonth = $thisMonth->format('F Y');
body {
  font-family: Arial, sans-serif;
  font-size: 14px;
}
a {
  text-decoration: none;
}
table {
  margin: 15px auto;
  border: 1px solid #ddd;
  border-collapse: collapse;
}
th {
  background: #eee;
}
th, td {
  padding: 7px;
  text-align: center;
}

.youbi_0{
  color: red;
}
.youbi_6 {
  color: blue;
}
.today {
  font-weight: bold;
}
.gray {
  color: #dedede;
}

functions, config

%e7%84%a1%e9%a1%8c

<?php

function connectDb(){
  try {
    return new PDO(DSN, DB_USER, DB_PASSWORD);
  } catch (PDOException $e){
    echo $e->getMessage();
    exit;
  }
}

function h($s){
  return htmlspecialchars($s, ENT_QUOTES, "UTF-8");
}

function setToken(){
  if (!isset($_SESSION['token'])){
    $_SESSION['token'] = sha1(uniqid(mt_rand(), true));
  }
}

function checkToken(){
  if (empty($_POST['token']) || $_POST['token'] != $_SESSION['token']){
    echo "不正な処理です!";
    exit;
  }
}
/*

create database contact_php;
grant all on contact_php.* to dbuser@localhost identified by 'xxxx';

use contact_php

create table entries (
  id int not null auto_increment primary key,
  name varchar(255),
  email varchar(255),
  memo text,
  created datetime,
  modified datetime
);

alter table entries add status enum('active', 'deleted') default 'active' after memo;
*/

define('DSN','mysql:host=localhost;dbname=contact_php');
define('DB_USER','dbuser');
define('DB_PASSWORD','xxxx');

define('SITE_URL','http://192.168.33.10:8000');
define('ADMIN_URL', SITE_URL.'/admin/');

error_reporting(E_ALL & ~E_NOTICE);
session_set_cookie_params(0, '');

edit, delete

<?php

require_once('../config.php');
require_once('../functions.php');

session_start();
$dbh = connectDb();

if (preg_match('/^&#91;1-9&#93;&#91;0-9&#93;*$/', $_GET&#91;'id'&#93;)){
    $id = (int)$_GET&#91;'id'&#93;;
} else{
    echo "不正なIDです!";
    exit;
}

if ($_SERVER&#91;'REQUEST_METHOD'&#93; != "POST"){
  setToken();

  $stmt = $dbh->prepare("select * from entries where id = :id limit 1");
  $stmt->execute(array(":id" => $id));
  $entry = $stmt->fetch() or die("no one found!");
  $name = $entry['name'];
  $email = $entry['email'];
  $memo = $entry['memo'];

} else {
  checkToken();

  $name = $_POST['name'];
  $email = $_POST['email'];
  $memo = $_POST['memo'];

  $error = array();

  if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
    $error['email'] = 'メールアドレスの形式が正しくありません';
  }
  if($email == ''){
    $error['email'] = 'メールアドレスを入力してください';
  }
  if($memo == ''){
    $error['memo'] = '内容を入力してください';
  }
  if(empty($error)){

    $sql = "update entries set
            name = :name,
            email = :email,
            memo = :memo,
            modified = now()
            where id = :id";
     $stmt = $dbh->prepare($sql);
     $params = array(
       ":name" => $name,
       ":email" => $email,
       ":memo" => $memo,
       ":id" => $id
     );
     $stmt->execute($params);

    header('Location: '.ADMIN_URL);
    exit;
  }
}

?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>データの編集</title>
</head>
<body>
  <h1>データの編集</h1>
  <form method="POST" action="">
    <p>お名前:<input type="text" name="name" value="<?php echo h($name); ?>"></p>
    <p>メールアドレス*:<input type="text" name="email" value="<?php echo h($email); ?>">
    <?php if($error&#91;'email'&#93;){ echo h($error&#91;'email'&#93;); } ?></p>
    <p>内容*:</p>
    <p><textarea name="memo" cols="40" rows="5"><?php echo h($memo); ?></textarea>
    <?php if($error&#91;'memo'&#93;){ echo h($error&#91;'memo'&#93;); } ?></p>
    <p><input type="submit" value="更新"></p>
    <input type="hidden" name="token" value="<?php echo h($_SESSION&#91;'token'&#93;); ?>">
  </form>
  <p><a href="<?php echo ADMIN_URL; ?>">戻る</a>
</body>
</html>

delete

<?php

require_once('../config.php');
require_once('../functions.php');

$dbh = connectDb();

$id = (int)$_POST&#91;'id'&#93;;

$dbh->query("update entries set status = 'deleted' where id = $id");

echo $id;

thanks

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>ありがとうございました!</title>
</head>
<body>
  <h1>ありがとうございました!</h1>
  <p></p>
  <p><a href="index.php">お問い合わせフォームに戻る</a></p>
</body>
</html>