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