Cake.phpのインストール

Cakeは人気のあるphpのwebアプリケーションフレームワークです。centOSへのインストールは、まずcomposerをディレクトリに入れます。

curl -sS https://getcomposer.org/installer | php

次に、composerを使って、cakeをインストールしていきます。
公式サイトを参考にすると、エラーなくいけると思います。
http://book.cakephp.org/3.0/en/installation.html

[vagrant@localhost cake]$ php composer.phar create-project --prefer-dist cakephp/app myapp

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

myappに移動し、コマンドラインからサーバーを立ち上げます。

[vagrant@localhost myapp]$ bin/cake server -H 192.168.33.10 -p 8000

以下のように表示されれば、OKです。
%e7%84%a1%e9%a1%8c

完了したら、configファイルのapp.phpよりDatasourcesを探し、データベースの接続を設定していきます。東京のtimezoneは’timezone’ => ‘+09:00’,です。

加えて、bootstrap.phpでもタイムゾーンを変更します。

date_default_timezone_set('Asiz/Tokyo');

ログイン機能の実装

下のキャプチャのように、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;
  }

}

facebook SDK with composer

composerを使ったfacebook sdk
https://developers.facebook.com/docs/php/gettingstarted

[vagrant@localhost facebook]$ curl -sS https://getcomposer.org/installer | php
All settings correct for using Composer
Downloading 1.2.2...

Composer successfully installed to: /home/vagrant/facebook/composer.phar
Use it: php composer.phar

続いて、composer.jsonを作成

{
  "require" : {
    "facebook/php-sdk-v4" : "~5.0"
  }
}

その後、php composer.phar installで完了です。

jsonにautoloadを追加

{
  "require" : {
    "facebook/php-sdk-v4" : "~5.0"
  },
  "autoload":{
    "psr-4": {
      "MyApp\\": "lib/"
    }
  }
}

コマンドライン

[vagrant@localhost facebook]$ php composer.phar dump-autoload

twitter bot

まう、開発環境にcomposerをインストールします。

curl -sS https://getcomposer.org/installer | php

続いてtwitteroauthを入れます。

[vagrant@localhost bot]$ php composer.phar require abraham/twitteroauth

twitteroauthのHPに沿って、PHPを書いていきます。なお、 CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRETはtwitter developersのmy appから取得する必要があります。
https://twitteroauth.com/

<?php

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

// package
// -Composer

use Abraham\TwitterOAuth\TwitterOAuth;

$connection = new TwitterOAuth(
  CONSUMER_KEY,
  CONSUMER_SECRET,
  ACCESS_TOKEN,
  ACCESS_TOKEN_SECRET);
// $content = $connection->get("account/verify_credentials");
$content = $connection->get("statuses/home_timeline", ['count'=>3]);

var_dump($content);

crontabで指定した日時につぶやきをセットすることができます。

投票システム (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;
}

interactive Art

addEventListener(‘click’, function(e){})で、push(new Ball(x, y, r))します。ランダム関数の範囲をとる際には、return min + Math.floor(Math.random() * (max – min + 1))と書きます。また、ボールの移動は、x++, y++, バウンドは、x軸、y軸をそれぞれ-1をかけてあげます。

reference
.getBoundingClientRect():returns the size of an element and its position relative to the viewport.
setTimeout bind: http://stackoverflow.com/questions/2130241/pass-correct-this-context-to-settimeout-callback
Object.prototype: property represents the Object prototype object.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Intractive Art</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<style>
	</style>
</head>
<body>
	<canvas id="mycanvas" width="500" height="250">
	</canvas>
	<script>
	(function(){
		'use strict';

		var canvas;
		var ctx;
		var Ball;
		var balls = [];
		var Stage;
		var stage;

		canvas = document.getElementById('mycanvas');
		if (!canvas || !canvas.getContext) return false;
		ctx = canvas.getContext('2d');

		function rand(min, max){
			return min + Math.floor(Math.random() * (max - min + 1));
		}

		function adjustPosition(pos, r, max) {
			/*if (x - r < 0) x = r;
			if (y - r < 0) y = r;
			if (x + r > canvas.width) x = canvas.width - r;
			if (y + r > canvas.height) y = canvas.height - r;*/
			if (pos - r < 0){
				return r;
			} else if (pos + r > max){
				return max - r;
			} else {
				return pos;
			}

		}

		canvas.addEventListener('click', function(e){
			var x, y, r;
			var rect;
			/*x = rand(100, 400);
			y = rand(100, 200);*/
			rect = e.target.getBoundingClientRect();
			x = e.clientX - rect.left;
			y = e.clientY - rect.top;

			r = rand(0, 100) < 20 ? rand(50, 80) : rand(10, 35);

			/*if (x - r < 0) x = r;
			if (y - r < 0) y = r;
			if (x + r > canvas.width) x = canvas.width - r;
			if (y + r > canvas.height) y = canvas.height - r;*/
			x = adjustPosition(x, r, canvas.width);
			y = adjustPosition(y, r, canvas.height);
			balls.push(new Ball(x, y, r));
		});

		Ball = function(x, y, r){
			this.x = x;
			this.y = y;
			this.r = r;
			this.vx = rand(-10, 10);
			this.vy = rand(-10, 10);
			this.color = 'hsla(' + rand(50, 100)+ ', ' + rand(40, 80) + '%, ' + rand(50, 60) +'%, ' + Math.random() + ')';
		};

		/*var ball = new Ball(rand(100, 200), rand(100, 200), rand(10, 50));
		ball.draw();*/

		Ball.prototype.draw = function(){
			ctx.beginPath();
				ctx.arc(this.x, this.y, this.r, 0, Math.PI*2);
				ctx.fillStyle = this.color;
				ctx.closePath();
				ctx.fill();

		};

		Ball.prototype.move = function(){
			if (this.x + this.r > canvas.width || this.x - this.r < 0){
				this.vx *= -1;
			}
			if (this.y + this.r > canvas.height || this.y - this.r < 0){
				this.vy *= -1;
			}
			this.x += this.vx;
			this.y += this.vy;

		};

		Stage = function(){
			this.update = function(){
					var i;
					this.clear();
			
			for (i = 0; i < balls.length; i++){
				balls&#91;i&#93;.draw();
				balls&#91;i&#93;.move();
			}
			setTimeout(function(){
				this.update();
			}.bind(this), 30);

			};
			this.clear = function(){
				ctx.fillStyle = "#ecf0f1";
				ctx.fillRect(0, 0, canvas.width, canvas.height);
			}
		};

		stage = new Stage();
		stage.update();

	})();
	</script>
</body>
</html>

15puzzle

タイルを動かして、1-15の順番に並べるゲームで、各数字に割り当てるx座標y座標をMathfloorのwidth,heightで割って、配列しています。var moveCountの数字を動かすことによって、ゲームの難易度を変更します。

reference
clientX Property:returns the horizontal coordinate (according to the client area) of the mouse pointer when a mouse event was triggered.The client area is the current window.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>15puzzle</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<style>
	</style>
</head>
<body>
	<canvas id="stage" width="280" height="280">
		Canvas not supported ...
	</canvas>
	<script>
		(function(){
			'use strict';

			var canvas = document.getElementById('stage');
			var context;
			var image;
			var IMAGE_URL = '15puzzle.png';
			var tiles = [];
			var ROW_COUNT = 4;
			var COL_COUNT = 4;
			var PIC_WIDTH = 280;
			var PIC_HEIGHT = 280;
			var TILE_WIDTH = PIC_WIDTH / COL_COUNT;
			var TILE_HEIGHT = PIC_HEIGHT / ROW_COUNT;
			var UDLR = [
				[ 0, -1],
				[ 0, 1],
				[ -1, 0],
				[ 1, 0],
			];
			var moveCount = 3;

			function initTiles(){
				var row, col;
				for (row = 0; row < ROW_COUNT; row++){
					tiles&#91;row&#93; = &#91;&#93;;
					for (col = 0; col < COL_COUNT; col++){
						tiles&#91;row&#93;&#91;col&#93; = row * COL_COUNT + col;
					}
				}
				tiles&#91;ROW_COUNT - 1&#93;&#91;COL_COUNT - 1&#93; = -1;
			}

			function drawPuzzle(){
				var row, col;
				var sx, sy;
				var dx, dy;

				for (row = 0; row < ROW_COUNT; row++){
					for (col = 0; col < COL_COUNT; col++){
						dx = col * TILE_WIDTH;
						dy = row * TILE_HEIGHT;
						if (tiles&#91;row&#93;&#91;col&#93; === -1){
							context.fillStyle = '#eeeeee';
							context.fillRect(dx, dy, TILE_WIDTH, TILE_HEIGHT);
						}else {
						sx = (tiles&#91;row&#93;&#91;col&#93; % COL_COUNT) * TILE_WIDTH;
						sy = Math.floor((tiles&#91;row&#93;&#91;col&#93; / ROW_COUNT)) * TILE_HEIGHT;
				
						context.drawImage(image, sx, sy, TILE_WIDTH, TILE_HEIGHT, dx, dy, TILE_WIDTH, TILE_HEIGHT);
						}
						
					}
				}
	
				/*context.drawImage(image, 0, 0);*/
			}

			function checkResult(){
				var row, col;
				for (row = 0; row < ROW_COUNT; row++){
					for (col = 0; col < COL_COUNT; col++){
						if (row === ROW_COUNT -1 && col === COL_COUNT -1){
							return true;
						}
						if (tiles&#91;row&#93;&#91;col&#93; !== row * COL_COUNT + col){
							return false;
						}
				}
			}

			}
			function moveBlank(count){
				var blankRow, blankCol;
				var targetPosition;
				var targetRow, targetCol;

				blankRow = ROW_COUNT -1;
				blankCol = COL_COUNT -1;

				while(true){
				targetPosition = Math.floor(Math.random() * UDLR.length);
				targetRow = blankRow + UDLR&#91;targetPosition&#93;&#91;1&#93;;
				targetCol = blankCol + UDLR&#91;targetPosition&#93;&#91;0&#93;;
				if(targetRow < 0 || targetRow >= ROW_COUNT){
					continue;
				}
				if(targetCol < 0 || targetCol >= COL_COUNT){
					continue;
				}
				tiles[blankRow][blankCol] = tiles[targetRow][targetCol];
				tiles[targetRow][targetCol] = -1;
				blankRow = targetRow;
				blankCol = targetCol;
				if(!--count){
					break;
				}
				} 
			}

			if (!canvas.getContext){
				alert('canvas not supported ...');
				return
			}
			context = canvas.getContext('2d');

			image = document.createElement('img');
			image.src = IMAGE_URL;
			image.addEventListener('load', function(){
				initTiles();
				moveBlank(moveCount);
				drawPuzzle();
			});

			canvas.addEventListener('click', function(e){
				var x, y;
				var rect;
				var row, col;
				var i;
				var targetRow, targetCol;
				rect = e.target.getBoundingClientRect();
				x = e.clientX - rect.left;
				y = e.clientY - rect.top;
				row = Math.floor(y / TILE_HEIGHT);
				col = Math.floor(x / TILE_WIDTH);
				if (tiles[row][col] === -1){
					return;
				}
				 for (i = 0; i < UDLR.length; i++) {
       			 targetRow = row + UDLR&#91;i&#93;&#91;1&#93;;
      			  targetCol = col + UDLR&#91;i&#93;&#91;0&#93;;
      			  if (targetRow < 0 || targetRow >= ROW_COUNT) {
      		  	 	 continue;
     			   }
     			  if (targetCol < 0 || targetCol >= COL_COUNT) {
     		     	 continue;
    			    }
					if (tiles[targetRow][targetCol] === -1) {
         			 tiles[targetRow][targetCol] = tiles[row][col];
         			 tiles[row][col] = -1;
       				   drawPuzzle();
       				   if (checkResult()){
       				   	alert('Game Clear!')
       				   }
						break;
					}
				}
			});
		})();
	</script>
</body>
</html>