MVCでないWeb画面遷移 PHP

ログインの画面の遷移をそのまんまフレームワークとかMVCで作成せず作ってみる。

Controllerのフォルダは気分で作った。

f:id:kenji148:20191227232454p:plain

該当構成の場合、ビルトインサーバやApacheが動く環境で、接続する。   URLは下記のようになる。
例)
http://localhost:8000/login.php

ポイント
・URLの表示の仕方
・ボタン追下後の操作
・validateの仕方
・validate後、入力したデータを残すには?
postしたデータを一旦保管し、間違えた場合に備える。
・post後のページ遷移をどうするか。


上記ポイントで整理すれば下記コードは不要かもしれないが今は備忘記録のため掲載。


config.php
ここでとにかく表示に対するController、Modelの処理を詰め込んだ。

<?php

ini_set('display_errors', 1);

define('DSN', 'mysql:dbhost=localhost;dbname=sns_php');
define('DB_USERNAME', 'dbuser01');
define('DB_PASSWORD', 'password');

define('SITE_URL', 'http://' . $_SERVER['HTTP_HOST']);


session_start();

// require_once(__DIR__ . '/Exception/error.php');

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

class Index extends Controller {

  public function run() {
    if (!$this->isLoggedIn()) {
      // login
      header('Location: ' . SITE_URL . '/login.php');
      exit;
    }

    // get users info
    $userModel = new User();
    $this->setValues('users', $userModel->findAll());
  }

}

class Controller {

  private $_errors;
  private $_values;

  public function __construct() {
    $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));
  }

}

class Sinki extends Controller {

  public function run() {

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

  protected function postProcess() {

    $this->setValues('email', $_POST['email']);

    if ($this->hasError()) {
      return;
    } else {
          // create user
          try {
            $userModel = new User();

            $userModel->create([
              'email' => $_POST['email'],
              'password' => $_POST['password']
            ]);
          } catch (DuplicateEmail $e) {
            $this->setErrors('email', $e->getMessage());
            return;
          }
          // redirect to login
          header('Location: ' . SITE_URL . '/login.php');
          exit;
    }
  }


}



class Model {
  protected $db;

  public function __construct() {
    try {
      $this->db = new PDO(DSN, DB_USERNAME, DB_PASSWORD);
    } catch (PDOException $e) {
      echo $e->getMessage();
      exit;
    }
  }
}

class User extends Model {

  public function create($values) {
    $stmt = $this->db->prepare("insert into users (email, password, created, modified) values (:email, :password, now(), now())");
    $res = $stmt->execute([
      ':email' => $values['email'],
      ':password' => password_hash($values['password'], PASSWORD_DEFAULT)
    ]);
    if ($res === false) {
      throw new DuplicateEmail();
    }
  }

  public function login($values) {
    $stmt = $this->db->prepare("select * from users where email = :email");
    $stmt->execute([
      ':email' => $values['email']
    ]);
    $stmt->setFetchMode(\PDO::FETCH_CLASS, 'stdClass');
    $user = $stmt->fetch();

    if (empty($user)) {
      throw new Exception('該当ユーザなし');
    }

    if (!password_verify($values['password'], $user->password)) {
      throw new Exception('パスワードが違う');
    }

    return $user;
  }



}

Controller/Login.php
config.phpに全部埋め込みたかったが、分けたほうがわかりやすいとわかったので、下記だけだが分けた。

<?php

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


class Login extends Controller {

  public function run() {
    // if ($this->isLoggedIn()) {
    //   header('Location: ' . SITE_URL);
    //   exit;
    // }

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

  protected function postProcess() {
    // try {
    //   $this->_validate();
    // } catch (\MyApp\Exception\EmptyPost $e) {
    //   $this->setErrors('login', $e->getMessage());
    // }

    $this->setValues('email', $_POST['email']);

    if ($this->hasError()) {
      return;
    } else {
      try {
        $userModel = new User();
        $user = $userModel->login([
          'email' => $_POST['email'],
          'password' => $_POST['password']
        ]);
      } catch (Exception $e) {
        $this->setErrors('login', $e->getMessage());
        return;
      }

      // login処理
      // session_regenerate_id(true);
      // $_SESSION['me'] = $user;

      // redirect to home
      header('Location: ' . SITE_URL);
      exit;
    }
  }

  private function _validate() {
    if (!isset($_POST['token']) || $_POST['token'] !== $_SESSION['token']) {
      echo "Invalid Token!";
      exit;
    }

    if (!isset($_POST['email']) || !isset($_POST['password'])) {
      echo "Invalid Form!";
      exit;
    }

    if ($_POST['email'] === '' || $_POST['password'] === '') {
      throw new \MyApp\Exception\EmptyPost();
    }
  }

}

Exception\error.php
Exceptionのエラーを標準で使いたい場合、ここに記載する感じで作った。

<?php

class DuplicateEmail extends Exception {
  protected $message = 'Duplicate Email!';
}

index.php
ログインができたらという意味で仮に作った。

<?php

//ログインしたらとりあえずここに飛ばす
echo('indexの画面');
exit;

?>

login.php
とりあえずログインするための画面

<?php

// ログイン

require_once(__DIR__ . '/config.php');
//configにまとめてやってみたが、かなり長くなるので分けた)
require_once(__DIR__ . '/Controller/Login.php');


$app = new Login();

$app->run();

?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Log In</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div>
    <form action="" method="post" id="login">
      <p>
        <input type="text" name="email" placeholder="email" value="<?= isset($app->getValues()->email) ? h($app->getValues()->email) : ''; ?>">
      </p>
      <p>
        <input type="password" name="password" placeholder="password">
      </p>
      <p class="err"><?= h($app->getErrors('login')); ?></p>

      <div class="btn" onclick="document.getElementById('login').submit();">ログイン</div>
      <p class="fs12"><a href="/signup.php">新規登録</a></p>
    </form>
  </div>
</body>
</html>

signup.php
新規登録するための画面

<?php

// 新規登録

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

//親クラスのインスタンス化は不要
// new Controller();

//configにcontrollerクラス、modelクラスを作っている。
$app = new Sinki();

$app->run();

?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Sign Up</title>
</head>
<body>
  <div id="container">
    <form action="" method="post" id="signup">
      <p>
        <input type="text" name="email" placeholder="email" value="<?= isset($app->getValues()->email) ? $app->getValues()->email : ''; ?>">
      </p>
      <p class="err"><?= $app->getErrors('email'); ?></p>
      <p>
        <input type="password" name="password" placeholder="password">
      </p>
      <p class="err"></p>
      <div class="btn" onclick="document.getElementById('signup').submit();">新規登録</div>
      <p class="fs12"><a href="/login.php">ログイン/a></p>
    </form>
  </div>
</body>
</html>