20200807のPHPに関する記事は11件です。

【PHP入門】ログイン機能

はじめに

今回は、PHPとMySQLを使ってログイン機能を実装していきます。
※当ページは、【PHP入門】CRUD機能で作られたものを前提にしています。

バージョン

PHP:7.4.5
phpMyAdmin:5.0.2
MySQL:5.7.30

今回作成するファイル

html
- login.php
- login_process.php
- signup.php
- signup_process.php
- logout.php

model
- users.php

view
- login_view.php
- signup_view.php

テーブルの作成

bbs_user.sql
CREATE TABLE `bbs_users` (
  `user_id` int(11) NOT NULL,
  `name` varchar(20) NOT NULL,
  `password` varchar(20) NOT NULL,
  `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `bbs_users`
  MODIFY `user_id` int(11) NOT NULL AUTO_INCREMENT,
  ADD PRIMARY KEY (`user_id`);

定義

処理に使うものを追記

const.php
define('SIGNUP_URL', '/signup.php');
define('LOGIN_URL', '/login.php');
define('LOGOUT_URL', '/logout.php');

// 正規表現
define('REGEXP_ALPHANUMERIC', '/\A[0-9a-zA-Z]+\z/'); 

// 文字数制限
define('USER_NAME_LENGTH_MIN', 6);
define('USER_NAME_LENGTH_MAX', 20);
define('USER_PASSWORD_LENGTH_MIN', 6);
define('USER_PASSWORD_LENGTH_MAX', 20);

ユーザ登録

Modelの作成

users.phpを作成し、ユーザ登録・ログイン処理を記述

users.php
// ログイン
function get_user_by_name($db, $name){
    $sql = "
      SELECT
        user_id,
        name,
        password
      FROM
        bbs_users
      WHERE
        name = ?
    ";
    return fetch_query($db, $sql, array($name));
}

function login_as($db, $name, $password){
    $user = get_user_by_name($db, $name);
    if($user === false || $user['password'] !== $password){
        return false;
    }
    set_session('user_id', $user['user_id']);
    return $user;
}

// ユーザ登録
function regist_user($db, $name, $password) {
    if(is_valid_user($name, $password) === false){
        return false;
    }
    return insert_user($db, $name, $password);
}

// バリデーション
function is_valid_user($name, $password){
    // 短絡評価を避けるため一旦代入。
    $is_valid_user_name = is_valid_user_name($name);
    $is_valid_password = is_valid_password($password);
    return $is_valid_user_name && $is_valid_password ;
}

function is_valid_user_name($name) {
    $is_valid = true;
    if(is_valid_length($name, USER_NAME_LENGTH_MIN, USER_NAME_LENGTH_MAX) === false){
      set_error('ユーザー名は'. USER_NAME_LENGTH_MIN . '文字以上、' . USER_NAME_LENGTH_MAX . '文字以内にしてください。');
      $is_valid = false;
    }
    if(is_alphanumeric($name) === false){
      set_error('ユーザー名は半角英数字で入力してください。');
      $is_valid = false;
    }
    return $is_valid;
}

function is_valid_password($password){
    $is_valid = true;
    if(is_valid_length($password, USER_PASSWORD_LENGTH_MIN, USER_PASSWORD_LENGTH_MAX) === false){
      set_error('パスワードは'. USER_PASSWORD_LENGTH_MIN . '文字以上、' . USER_PASSWORD_LENGTH_MAX . '文字以内にしてください。');
      $is_valid = false;
    }
    if(is_alphanumeric($password) === false){
      set_error('パスワードは半角英数字で入力してください。');
      $is_valid = false;
    }
    return $is_valid;
}

// ユーザデータの挿入
function insert_user($db, $name, $password){
    $sql = "
      INSERT INTO
        bbs_users(name, password)
      VALUES (?,?);
    ";
    return execute_query($db, $sql, array($name, $password));
}
functions.php
// ログイン処理
function is_logined(){
  return get_session('user_id') !== '';
}

// バリデーション
function is_valid_length($string, $minimum_length, $maximum_length = PHP_INT_MAX){
  $length = mb_strlen($string);
  return ($minimum_length <= $length) && ($length <= $maximum_length);
}

function is_alphanumeric($string){
  return is_valid_format($string, REGEXP_ALPHANUMERIC);
}

function is_valid_format($string, $format){
  return preg_match($format, $string) === 1;
}

Viewの作成

※メッセージ表示のところは、繰り返し使用する為、別のファイルに記述し、それを読み込んでいます。

signup_view.php
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>ユーザ登録</title>
  </head>

  <body>
    <h1>ユーザ登録ページ</h1>

    <!-- メッセージ・エラーメッセージ -->
    <?php include VIEW_PATH. 'templates/messages.php'; ?>

    <!-- ログインフォーム -->
    <form method="post" action="signup_process.php">
      <div>
        <label>ユーザ名:</label>
        <input type="text" name="name">
      </div>
      <div>
        <label>パスワード:</label>
        <input type="password" name="password">
      </div>
      <input type="submit" value="新規登録">
      <input type="button" onclick="location.href='<?php print(LOGIN_URL); ?>'" value="ログインページへ">
    </form>
  </body>
</html>

Controllerの作成

signup.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH. 'functions.php';

session_start();

// ログインされていれば、掲示板に遷移
if(is_logined() === true){
    redirect_to(BBS_URL);
}

include_once VIEW_PATH. 'signup_view.php';
signup_process.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH. 'functions.php';
require_once MODEL_PATH. 'users.php';

session_start();

// ログインされていれば、掲示板に遷移
if(is_logined() === true){
    redirect_to(BBS_URL);
}

// Postされたものを定義
$name = get_post('name');
$password = get_post('password');

// データベース接続
$db = get_db_connect();

// ユーザ登録処理
try{
    $result = regist_user($db, $name, $password);
    if($result === false){
        set_error('ユーザ登録に失敗しました。');
        redirect_to(SIGNUP_URL);
    }
}catch(PDOException $e){
    set_error('ユーザ登録に失敗しました。');
    redirect_to(SIGNUP_URL);
}
set_message('ユーザ登録が完了しました。');

// ユーザ登録完了後、そのままログインして掲示板へ遷移
login_as($db, $name, $password);
redirect_to(BBS_URL);

ログイン

Viewの作成

※メッセージ表示のところは、繰り返し使用する為、別のファイルに記述し、それを読み込んでいます。

login_view.php
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>ログイン</title>
  </head>

  <body>
    <h1>ログインページ</h1>

    <!-- メッセージ・エラーメッセージ -->
    <?php include VIEW_PATH. 'templates/messages.php'; ?>

    <!-- ログインフォーム -->
    <form method="post" action="login_process.php">
      <div>
        <label>ユーザ名</label>
        <input type="text" name="name">
      </div>
      <div>
        <label>パスワード</label>
        <input type="password" name="password">
      </div>
      <input type="submit" value="ログイン">
      <input type="button" onclick="location.href='<?php print(SIGNUP_URL); ?>'" value="ユーザ登録ページへ">
    </form>
  </body>
</html>

Controllerの作成

login.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH . 'functions.php';

session_start();

// ログインされていれば、掲示板に遷移
if(is_logined() === true){
  redirect_to(BBS_URL);
}

include_once VIEW_PATH . 'login_view.php';
login_process.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH. 'functions.php';
require_once MODEL_PATH. 'users.php';

session_start();

// ログインされていれば、掲示板に遷移
if(is_logined() === true){
    redirect_to(BBS_URL);
}

// Postされたものを定義
$name = get_post('name');
$password = get_post('password');

// データベース接続
$db = get_db_connect();

// ログイン処理
$user login_as($db, $name, $password);
if($user === false){
    set_error('ログインに失敗しました。');
    redirect_to(LOGIN_URL);
}
set_message('ログインしました。');
redirect_to(BBS_URL);

ログアウト

Modelの作成

users.php
// ログイン状況
function get_user($db, $user_id){
    $sql = "
      SELECT
        user_id, 
        name,
        password
      FROM
        bbs_users
      WHERE
        user_id = ?
    ";
    return fetch_query($db, $sql, array($user_id));
}

function get_login_user($db){
    $login_user_id = get_session('user_id');  
    return get_user($db, $login_user_id);
}  

Viewの作成

bbs_view.php
<p>ようこそ、<?php print($user['name']); ?>さん。</p>
<a href="<?php print(LOGOUT_URL);?>">ログアウト</a>

Controllerの作成

logout.php
<?php
require_once '../conf/const.php';
require_once MODEL_PATH. 'functions.php';

session_start();

$_SESSION = array();
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
  $params["path"],
  $params["domain"],
  $params["secure"],
  $params["httponly"]
);

session_destroy();

redirect_to(LOGIN_URL);
bbs.php
require_once MODEL_PATH. 'users.php';

// ログインされてなければ、ログインページへ遷移
if(is_logined() === false){
    redirect_to(LOGIN_URL);
}  

$user = get_login_user($db);

参考

session_get_cookie_params

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSでLAMP環境を構築して、Laravelアプリをデプロイする

今回はAWSのEC2、RDSを用いてLAMP環境を構築して、Laravelアプリをデプロイする手順を紹介します。

今回の流れ

1.RDSを用いてDBサーバー作成
2.EC2を用いてWEBサーバーを作成
3.サーバー間の連携
4.アプリのデプロイ

前提条件

・Macを使用
・AWSアカウントを作成済みであること
・GitHubのリモートリポジトリに、開発済みのLaravelアプリが置いてあること

1.RDSを用いてDBサーバー作成

コンソールにサインインした後、AmazonRDSに移動します。
スクリーンショット 2020-08-07 9.22.21.png
続いてオレンジ色のCreate databaseボタンをクリックします。
スクリーンショット 2020-08-07 9.24.32.png
使用するデータベースエンジンを選びます。今回はMySQLを選択します。
バージョンはよしなに選択してください。
スクリーンショット 2020-08-07 9.28.31.png
今回は無料利用枠のものを作成します。
本番環境の場合は適したプランを選択してください。
スクリーンショット 2020-08-07 9.33.25.png
インスタンス名、マスターユーザー名、パスワードを設定します。
後ほど使いますので、安全な場所にメモしておきましょう。
スクリーンショット 2020-08-07 9.35.39.png
インスタンスのスペック、ストレージの容量等を設定します。
今回はデフォルト設定で進めますが、本番環境の場合はよしなに選択してください。
スクリーンショット 2020-08-07 9.37.05.png
ネットワークの設定を行います。
VPCを作成済みの方は作成したVPCを、そうでない方はデフォルトのVPCを設定しましょう。
スクリーンショット 2020-08-07 9.43.13.png
最後にオプションの設定をします。
RDSインスタンス初期化時に自動で作られるデータベースの名前を設定します。
今後データベースに接続する際は、ここで設定したデータベース名を使用します。スクリーンショット 2020-08-07 9.51.15.png
スクリーンショット 2020-08-07 9.51.05.png
オレンジ色のCreate databaseボタンをクリックして設定したデータベースを作成します。
スクリーンショット 2020-08-07 12.28.58.png

2.EC2を用いてWEBサーバーを作成

コンソールからAmazon EC2に移動して、青色のLaunch Instanceボタンをクリックします。
スクリーンショット 2020-08-07 12.36.05.png
使用するマシンのイメージを選択します。
今回はAmazon Linux 2 AMIを用います。スクリーンショット 2020-08-07 12.39.41.png
EC2インスタンスタイプを設定します。
CPU、メモリ、ストレージ、ネットワークパフォーマンス等を考慮してよしなに選択します。
今回はt2.microを選択します。(無料利用枠の対象です)スクリーンショット 2020-08-07 13.45.57.png

スクリーンショット 2020-08-07 12.48.01.png
詳細の設定です。
VPCやサブネット等を設定できますが、今回はとりあえずVPCのみ設定しておきます。
RDSで設定したものと同じVPCを選択してください。
スクリーンショット 2020-08-07 12.50.42.png
ストレージの設定をします。
他にインスタンスを作成されない場合は30GBまでは無料枠です。
今回はデフォルトの8GBで進めていきます。
スクリーンショット 2020-08-07 12.56.42.png
続いてタグの設定ですが今回は未設定のまま進み、セキュリティグループの設定をします。
EC2インスタンスへのアクセス許可、制限を指定します。
まずはSSH接続の設定です。
Create a new security groupを選択し、新しいグループを作成します。
TypeのプルダウンからSSHを選択し、SourceのプルダウンからはMyIPを選択しましょう。
次にHTTP接続です。
Add Ruleをクリックした後、TypeのプルダウンからHTTPを選択し、SourceのプルダウンからはMyIPを選択しましょう。
今回は自分のIPからのみ接続できる設定にします。
本番環境で不特定多数のアクセスを集める場合はHTTPのSourceをAnywhereにしてください。
スクリーンショット 2020-08-07 13.45.57.png
Review and Launchをクリックし確認画面に遷移し、設定内容に問題がなければLaunchをクリックしましょう。
最後に、SSH接続するためのキーペアを作成します。
Create new key pairを選択し、キーペアの名前を設定します。
設定後、Download Key Pairをクリックして.pemファイルをダウンロードします。
このファイルがなければSSH接続できないので、安全な場所で保管してください。
青色のボタンLaunch Instanceをクリックしてインスタンスを起動します。
スクリーンショット 2020-08-07 13.59.49.png

3.サーバー間の連携

まずは先に作成したRDSの設定を変更し、EC2インスタンスからRDSインスタンスへのアクセスを可能にします。
AmazonRDSに移動します。
スクリーンショット 2020-08-07 9.22.21.png
左側のダッシュボードのDatabasesをクリックして、先に作成したデータベースを選択します。
Connectivity & securityタブのセキュリティグループをクリックします。
スクリーンショット 2020-08-07 14.11.54.png
デフォルトではEC2インスタンスからRDSインスタンスへのアクセスは許可されていません。
Inboundタブを開きeditをクリックしてセキュリティグループの編集を行います。
スクリーンショット 2020-08-07 14.22.01.png
TypeをMySQL/Auroraに、SourceをCustomeに設定し先ほど作成したEC2のセキュリティグループの名前を入力します。
利用可能なセキュリティグループが表示されるので、先ほど作成したものを選択します。自動で値が入力されます。
青色のSavaボタンをクリックし設定を反映させます。
スクリーンショット 2020-08-07 14.58.00.png
続いてEC2インスタンスにSSH接続していきます。
ターミナル上のキーペアがあるディレクトリで以下のコマンドを実行します。

ssh -i .pemファイル名 ec2-user@パブリックIP

パブリックIPは、接続するインスタンスのこちらをコピーペーストしましょう。
スクリーンショット 2020-08-07 15.15.22.png
EC2インスタンスにSSH接続できたら、RDSに接続するための準備をしていきます。
MySQLをインストールしたいのですが、MariaDBがデフォルトでインストールされていると競合してしまうので、アンインストールしておきます。

$ sudo yum remove mariadb-libs

MySQLのリポジトリを追加して、MySQLをインストールします。

$ sudo yum localinstall https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
$ sudo yum install mysql

MySQLのホスト名を環境変数に設定して、データベースに接続します。
エンドポイントはRDSインスタンスのホスト名を入力します。
ユーザー名、パスワード、DB名は先に作成したものです。

$ export MYSQL_HOST=エンドポイント
$ mysql --user=ユーザー名 --password=パスワード DB名

スクリーンショット 2020-08-07 17.11.38.png
Laravelアプリのためのユーザーを作成し、データベースへのアクセス権限を付与します。

mysql> CREATE USER 'ユーザー名' IDENTIFIED BY 'パスワード';
mysql> GRANT ALL PRIVILEGES ON DB名.* TO ユーザー名;
mysql> FLUSH PRIVILEGES;
mysql> Exit

次にEC2インスタンスにApacheとPHPをインストールします。
まずはApacheのインストールです。

$ sudo yum install -y httpd
$ sudo service httpd start

パブリックIPアドレスにアクセスして、以下の画面が表示されていればインストール成功です。
スクリーンショット 2020-08-07 17.47.44.png
PHP並びにLaravelアプリに必要な拡張モジュール等をインストールしていきます。
今回のLaravelのバージョンは5.5なので、対応したPHP拡張モジュールも合わせてインストールしておきます。
また、自分の開発したアプリに必要な拡張モジュールがあれば適宜追加でインストールしてください。

$ sudo amazon-linux-extras install -y php7.2
$ sudo yum -y install php-openssl php-pdo php-mbstring php-tokenizer php-xml

これでLAMP環境を構築することができました。

4.アプリのデプロイ

Laravelアプリのデプロイ、デプロイのための最終準備をしていきます。
まずはEC2インスタンスにGitをインストールします。

$ sudo yum -y install git

続いて/var/www/htmlに移動して、完成済みのLaravelアプリをgit cloneします。

$ cd /var/www/html
$ sudo git clone URL

Apacheの設定を変更していきます。
/etc/httpd/confに移動して、httpd.confを編集します。

$ cd /etc/httpd/conf
$ vi httpd.conf

以下が編集箇所です。
119行目付近でドキュメントルートを変更し、アクセスがあった際にアプリ直下のpublicディレクトリにファイルを探しにいってくれるようにします。
131行目付近、151行目付近でpublicディレクトリ内での設定の変更を有効にします。
最終行でmod_rewriteを許可することでURLの書き換えを可能にします。

#119行目付近 
DocumentRoot "/var/www/html/Laravelアプリ/public"
#131行目付近
<Directory "/var/www/html/Laravelアプリ/public">
#151行目付近
AllowOverride All
#最終行に追加
LoadModule rewrite_module modules/mod_rewrite.so

変更後はApacheを再起動させます。

$ sudo systemctl restart httpd

続いてComposerをインストールします。
/usr/local/bin/にインストールしてファイル名を変更することで、どこからでもcomposerコマンド一つで呼び出し可能にします。

$ cd /usr/local/bin/
$ sudo php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ sudo php -r "if (hash_file('sha384', 'composer-setup.php') === 'e5325b19b381bfd88ce90a5ddb7823406b2a38cff6bb704b0acc289a09c8128d4a8ce2bbafcd1fcbdc38666422fe2806') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
Installer verified
$ sudo php composer-setup.php
$ sudo php -r "unlink('composer-setup.php');"
$ sudo mv composer.phar composer

最後の仕上げです。
アプリの環境変数の設定等を行っていきます。
まずはcomposer.lockファイルを基にパッケージをインストールします。
ディレクトリの権限も変更しておきます。こちらの変更はアプリ起動完了後によしなに変更しても良いかと思います。

$ cd /var/www/html
$ chmod 777 Laravelアプリ
$ cd Laravelアプリ
$ chmod 777 bootstrap/cache
$ chmod 777 storage/framework/sessions
$ chmod 777 storage/framework/views
$ chmod 777 storage/logs vendor
$ composer install

.env.exampleファイルeを基に.envファイルを作成します。

$ cp .env.example .env
$ vi .env

先に作成したRDCインスタンスを基に、DB接続に関する変数を定義していきます。
ユーザー名、パスワードはRDCインスタンスに接続して登録したものを設定します。

DB_CONNECTION=mysql
DB_HOST=ホスト名(エンドポイント)
DB_PORT=3306
DB_DATABASE=DB名
DB_USERNAME=ユーザー名
DB_PASSWORD=パスワード

artisanコマンドを叩いてアプリケーションキーを作成します。
マイグレーションも実行しておきます。(開発済みのアプリなのでマイグレーションファイルがあることが前提です)

php artisan key:generate
php artisan migrate

これでパブリックIPアドレスにアクセスすれば、アプリケーションが動作しているはずです!
ここまでお疲れ様でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelの基礎学習 with XAMPP

この記事の解説は独自の解釈が多いため間違っている可能性もあります。そのような箇所があれば指摘して頂けると幸いです。

目標

Laravelで簡易的なサイトが作れる程度の基礎を習得する

環境

  • Windows 10 home
  • XAMPP
  • Laravel

Laravelの勉強

まずは初心者のためのLaravel入門 - libroを軸に勉強を進めていく。
学習の手順としては入れ替えた方が良い箇所もあるかもしれないが、自分にとって親しみやすい順で進めていく。

Laravelのインストール

Composerのインストール

LaravelのインストールにはComposerというパッケージ管理ツールを使う。
PHPで使用するソフトウェアやアプリケーションをインストールしたい時に、そのソフトウェアなどが動くのに必要な依存関係のある他のソフトウェアなども一緒にインストールしてくれる便利なツール。
Composer 公式からダウンロードして、PATHを通して、ターミナルなどで次のような表示がされればOK。
screenshot.jpg

Laravelインストーラーの導入

Laravelでは設定ファイルやコードなどを1つのプロジェクトとしてまとめて作業を行う。
ディレクトリやフォルダと同じ認識。

では、Laravelのプロジェクトを作成していく。
今後もLaravelプロジェクトを作成することを考えて、ComposerでLaravelインストーラーを導入してからLaravelのプロジェクトを作成する。
Laravelインストーラーは下記のコマンドをターミナル実行して、インストールする。

$ composer global require "laravel/installer"

Laravelインストーラーのダウンロードが終わったら、赤下線部分をエクスプローラーで開く。
screenshot.jpg
そこから~\vendor\binまで移動し、そこまでのパスをコピーする。

C:\(各自のパス)\AppData\Roaming\Composer\vendor\bin

その後、Windows10の左下にある検索窓から「環境変数」を入力してシステム環境変数の編集を開く

システムのプロパティが開けたら下の環境変数(赤枠部)をクリック。
screenshot.jpg

その後、下にあるシステム環境変数(S)からPathを探してダブルクリック。
すると、環境変数名の編集が開けるので、右上の新規をクリックし、先程のパスを貼り付けて、OKを押す。

Laravelプロジェクトの作成

ターミナルに戻り、下記コマンドを実行。

$ laravel new blog(プロジェクト名)

これでLaravelのプロジェクトが作成される。
今後、Laravelのプロジェクトを作成したいときは、$ laravel new (プロジェクト名)をターミナルで実行するだけで作成できるようになった。

作成できたプロジェクトを実際に実行してみる。
ターミナルから以下のコマンドを実行。

$ cd (プロジェクト名)
$ php artisan serve

screenshot.jpg

$ php artisan serveLaravelの内蔵サーバーを起動できる。
つまり、このコマンドを利用することでローカルのアプリケーションとしてプロジェクトの状態確認が可能に。
ターミナルに表示された http://127.0.0.1:8000 にアクセスしてみると、Laravelと中央に大きく書かれたサンプルページが開かれる。
ターミナルでCtrl + Cを押すとサーバーを閉じる。

プロジェクト(フォルダ)構成について

プロジェクトを作成した際、一緒にインストールされたファイルなどはLaravelをスタートしよう(4/5):初心者のためのLaravel入門 - libroに解説が載っている。
ただ、この記事の情報は古いため、必要があれば下記コマンドでLaravelのバージョンなどを確認し、公式ドキュメントなどで各自フォルダ構成について調査を。

$ php artisan --version

ルーティング処理

ルーティング処理: どのアドレスでアクセスされたら、どの処理を返すかを定義するもの

Laravelでは(プロジェクト名)\routes\web.phpでルーティング処理が指定されており、実際にファイルを確認してみると、以下のような宣言がある。

(プロジェクト名)\routes\web.php
Route::get('/', function () {
    return view('welcome');
});

上記のコードをRoute::メソッド(第一引数, 第二引数)として解説を行う。

  • Route: クラス名。::の後に続くメソッドを呼び出して処理を実行する。
  • メソッド: Routeクラスで定義されている処理から、どの処理を実行するかを指定する。ここによって次の第一引数の値が変わる。
  • 第一引数: メソッドによって値が変わる。(例: getメソッドではアドレスを表すテキスト。groupメソッドではアドレス情報をまとめた配列になる)
  • 第二引数: 第一引数にアクセスされた際に返す処理。web.phpではファイル名が指定されているがテキストなども返すことができる。

以上の解説を踏まえて、現在のweb.phpを見てみると、/(ルート)にアクセスされたらview(welcome)を返すルーティング処理であることがわかる。
このルーティング処理で呼び出されるview(welcome)がどこにあるか、どのファイルかという質問に対する答えは、(プロジェクト名)\resources\views\welcome.blade.php

view()関数については次章で説明する。
welcome.blade.phpbladeという見慣れない拡張子があるが、これはLaravelで利用できるテンプレートの仕組み。
この仕組みを利用することで、blade拡張子がついたファイル同士でコードを共有しあったり、Laravelに搭載されている機能を簡単に呼び出したりすることができるようになる。

ルーティング処理についてわかったところで、Hello,World!!を表示するようにweb.phpを変更してみる。
この後の作業で使用するため、変更前のコードをコメントアウトなどして残しておく。

(プロジェクト名)\routes\web.php
Route::get('/', function () {
    return 'Hello,World!!';
});

screenshot.jpg

表示用のファイル(テンプレート)について

web.phpで特定のURLにアクセスされた際の処理を定義できることが分かった所で、その結果を表示するページを作成していく。
今回は簡単なPHPファイルを作成するが、welcome.blade.phpのようなファイルを作成することもある。むしろ、そちらが主流。そのような.blade.php拡張子ファイルはテンプレートと呼ばれ、この先で作成するため覚えておいてほしい。

では、表示用のページとなるファイルから作成していく。
(プロジェクト名)\resources\views\template.phpを作成し、下記の内容にする。

(プロジェクト名)\resources\views\template.php
<!DOCTYPE HTML>
<html>
    <head>
        <title>template</title>
        <style>
            body{color:gray;}
            h1{font-size:18pt; font-weight:bold;}
        </style>
    </head>
    <body>
        <h1>Template</h1>
        <p><?php echo $message; ?></p>
    </body>
</html>

次に、web.phpでこのファイルをを表示するルーティング処理を定義する。

(プロジェクト名)\routes\web.php
Route::get('/template', function () {
    return view('template',['message' => 'Study Template']);
});

ルーティング処理の内容は/templateにアクセスされたらtemplate.phpを返すというものだが、view()の中に連想配列が指定されている。

ここでルーティング処理の所で飛ばしたview()関数の説明をする。
view()関数はview(第一引数, 第二引数)という形をとり、次のように宣言する。

  • 第一引数: ファイル名、テンプレート名を指定する。拡張子を除いた名前を宣言する
  • 第二引数: 第一引数に渡す値などを指定する。今回では連想配列を用いて、template.phpの変数$messageStudy Templateという文字列を渡している。

以上のファイル、ルーティング処理の作成によって表示されるページは次のようなものとなる。
http://127.0.0.1:8000/template

screenshot.jpg

コントローラー

(プロジェクト名)\routes\web.phpにルーティング処理を定義することで、(プロジェクト名)\resources\views\にある表示用のファイルやテンプレートを呼び出せるようになったがある問題が潜んでいる。
それはview()の第二引数を利用する処理が増えると、web.phpのコードが複雑になることだ。
テンプレートの作成では第二引数に指定する連想配列のキーと値は1つだけだったが、web.phpで宣言するルーティング処理が増え、連想配列の数も増えれば見づらく、読みにくいコードとなってしまう。

そのような問題を防ぐために使用されるのがコントローラーだ。
実際に作成しながら解説をしていく。

コントローラーは(プロジェクト名)\app\Http\Controllersに作成する。
今回はその階層にTemplateController.phpというファイルを作成し、次の内容にする。

(プロジェクト名)\app\Http\Controllers\TemplateController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class TemplateController extends Controller
{
    public function getIndex()
    {
        return view('template', ['message' => 'Study Template']);
    }
}
?>

そして、web.phpRoute::get('/template', ~を下記のように変更する。

(プロジェクト名)\routes\web.php
Route::get('/template','TemplateController@getindex');

Routeクラスの第二引数に指定されているTemplateController@getindexTemplateControllergetIndex()メソッドを呼び出すことを宣言している。

TemplateController.phpのようなコントローラーの仕組みを説明をしていく。
新規作成したコントローラーのファイルではuse App\Http\Controllers\Controller;App\Http\Controllers\Controllerというクラスを継承することを忘れずに宣言する。
public function getIndex()で宣言されているgetIndex()は、Route::get()などのGETアクセスでindexというアドレスにアクセスした際に呼び出されるメソッドであることを定義している。
このTemplateController.php自体がweb.phpのルーティング処理において、Route::get()/templateに割り当てられているため、/template/index(トップページ)にアクセスすると、getIndex()が呼び出され、view()で定義した処理が実行される仕組みとなっている。

コントローラーで指定するメソッド(getIndex()など)はHTTPメソッドの種類 + アクセスするアドレスによって名前が自動的に決まるため、このルールを覚えておく必要がある。

http://127.0.0.1:8000/template にアクセスして、エラーが表示されていないか確認。

Requestクラス

ここではRequestクラスを利用して、フォームを使用したページ間でのデータの送受信ができるようにする。
この記事の軸にしていたサイトの情報が古かったので、ここではLaravel5 チュートリアル ブログもどきを作る(2) ブログ記事投稿フォームの作成 - Qiitaをメインに進めていく。

まずはテンプレートの作成から。
GETメソッド以外でフォームを利用する場合、Laravelの仕様でCSRF(ログイン状態で悪意あるURLにアクセスするとログイン情報が盗まれ悪用される事)の対策をしないとエラーが発生する。
その対策として活用できるのがLaravelの機能であるbladeテンプレートだ。
(プロジェクト名)\resources\views\form.blade.phpを作成し、次の内容にする。

(プロジェクト名)\resources\views\form.blade.php
<!DOCTYPE HTML>
<html>
    <head>
        <title>Form</title>
        <style>
            body{color:gray;}
            h1{font-size:18pt; font-weight:bold;}
        </style>
    </head>
    <body>
        <h1>フォームを利用したデータの送受信</h1>
        <p>{{ $message }}</p>
        <form method="POST" action="/form">
            {{ csrf_field() }}
            <input type="text" name="str">
            <input type="submit">
        </form>
    </body>
</html>

{{ }}bladeテンプレートの記法。
{{}}自体は<?php echo ~ ?>の代わりで、{{ csrf_field() }}はこれだけでLaravelにおけるCSRF対策の機能を呼び起こし、利用できる。

続いて、コントローラーを作成する。
(プロジェクト名)\app\Http\ControllersFormController.phpを作成し、次の内容にする。

(プロジェクト名)\app\Http\Controllers\FormController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class FormController extends Controller
{
    public function getIndex(Request $request)
    {
        return view('form', ['message' => 'データを入力してください。']);
    }
    public function postIndex(Request $request)
    {
        $res = "入力内容: ".$request -> input('str');
        return view('form', ['message' => $res ]);
    }
}
?>

postIndex()はテンプレートの章でも説明したように、POSTアクセスでindexに接続した際に実行される処理がそれ以降に記載されている。
そのため、POSTアクセスが行われていない何もデータが入力されていない時に表示する用のページとして、getIndex()のメソッドも作成しておくことも必要。

そして、今回の主題となるのがRequestクラスを利用した(Request $request)だ。
Requestクラスではユーザーからのアクセスを始めとする各種情報を管理している。
その内容を$requestに入れ、今回はinput('str')という形でデータを取り出している。
inputに関するデータはinput(name属性)で取り出せるため、このような記述となる。

(プロジェクト名)\routes\web.phpに次のルーティング処理を追記する。

web.php
Route::get('/form','FormController@getindex');
Route::post('/form','FormController@postindex');

ここで意図した設計で稼働しているか確認してみる。
http://127.0.0.1:8000/form にアクセス。

screenshot.jpg

この状態で送信を押すと、

screenshot.jpg

このように変化する。

Requestクラスの基本(3/5):初心者のためのLaravel入門 - libro 以降ではクエリやURLの取得、リダイレクトについて記載されているため、確認してみては。

データベースとの連携

ここではXAMPPMySQLを利用して、Laravelとデータベースを連携させる。

まずはデータベースの準備から。
XAMPPを起動したら、XAMPPのコントロールパネルにおけるMySQLAdminをクリックして、phpMyAdminに接続する。

screenshot.jpg

phpMyAdminにログインできたら、データベースタブから下記画像のように新規データベースを作成する。(画像ではデータベース名が頭文字が大文字だったが作成したら小文字になった。)
screenshot.jpg

そして、新規テーブルを作成する。
好きな名前とカラム数を設定してテーブルを作成し、
screenshot.jpg

各カラムの設定を行い、
screenshot.jpg

サンプルデータを何個か入れる。
screenshot.jpg

ここまで(↓)設定できたら、次はLaravel側の設定に移る。
screenshot.jpg

今回はMySQLを使用するが、他のデータベースを利用したい場合は、(プロジェクト名)\config\database.phpを開いて、下記の部分を使用したいデータベースに変える。

(プロジェクト名)\config\database.php
'default' => env('DB_CONNECTION', 'mysql'),

どのデータベースを使用するか設定できたら、(プロジェクト名)\.envを開いて、下記にデータベース情報を入力していく。
デフォルトで値が入っているが、必要があれば変更する。

(プロジェクト名)\.env
DB_CONNECTION=
DB_HOST=
DB_PORT=
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=

ここからはデータベースに接続し、値を取得、ページに表示するまでを説明していく。

コントローラーの作成から行っていく。
(プロジェクト名)\app\Http\ControllersDatabaseController.phpを作成し、下記の内容にする。

(プロジェクト名)\app\Http\Controllers\DatabaseController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

use DB; 
use App\Http\Requests;
use App\Http\Controllers\Controller;

class DatabaseController extends Controller
{
    public function getIndex(Request $request)
    {
        $data = DB::select('select * from sample');
        return view('database', ['message' => 'データベース名: sample','data' => $data]);
    }
}
?>

use DB; を忘れないように注意。
DB::select はデータベースに対してSELECTのクエリ文を実行するための関数。

select以外にもDBクラスにはデータベースを操作するためのメソッドがあり、それらを CRUD処理 と呼ぶ。
CRUD処理 とは、Create(生成) , Read(読み取り) , Update(更新) , Delete(削除) の処理における頭文字を並べたもの。
それぞれのDBクラスにおけるメソッドはCreate(生成) = DB:insert() , Read(読み取り) = DB:select() , Update(更新) = DB:update() , Delete(削除) = DB:delete() となるので覚えておく。

余談となるが、複数人がページやサイトにアクセスしていて、同時にデータベースにCRUD処理を実行した場合、UpdateDeleteが同時に実行する命令が起きるようなデータの不整合が起きる可能性がある。
そのような事態に陥るのを防ぐために行われるのが トランザクション で、DB::beginTransaction(); から DB::commit(); の間に書かれた処理が実行されている間は他の命令が実行されることを防ぐことができるようになる。
本番環境でデータの書き換えを行うような処理を定義する場合は忘れずに記述するようにしておく。

本題に戻って、コントローラーが取得したデータベースのデータを表示するページを作成していく。
(プロジェクト名)\resources\views\database.blade.phpを作成し、下記のような内容に。

(プロジェクト名)\resources\views\database.blade.php
<!DOCTYPE HTML>
<html>
    <head>
        <title>Form</title>
        <style>
            body{color:gray;}
            h1{font-size:18pt; font-weight:bold;}
              th{color:white; background:#999;}
              td{color:black; background:#eee; padding:5px 10px;}
        </style>
    </head>
    <body>
        <h1>Laravelとデータベースの連携</h1>
        <p>{{ $message; }}</p>
        <table>
            <tr>
                <th>ID</th>
                <th>NAME</th>
                <th>PASS</th>
                <th>CREATE_DAY</th>
            </tr>
            @foreach($data as $value)
            <tr>
                <td>{{ $value -> id; }}</td>
                <td>{{ $value -> name }}</td>
                <td>{{ $value -> pass }}</td>
                <td>{{ $value -> create_day }}</td>
            </tr>
            @endforeach
    </table>
    </body>
</html>

@foreach($data as $value)でコントローラーが取得したデータベースの各値が入っている$data$valueに移す。
{{ $value -> (カラム名) }}で各カラムに入っているデータを取得して表示している。

(プロジェクト名)\routes\web.phpに今回のルーティング処理を実装して、実際にページを確認してみる。

(プロジェクト名)\routes\web.php
Route::get('/database','DatabaseController@getindex');

自分の今までに掲載した画像の設定と同じ設定にすれば次のように表示されるはず。
http://127.0.0.1:8000/database

screenshot.jpg

ORM(Object/Relational Mapping)について

さっきは自分でデータベースを作成して値を取得、表示まで行ったが、仕事でも同じような状況で作業ができるとは限らない。
違う人がデータベースを作成する事なんて普通だし、そもそもデータベースを見れずに手探りでデータを取得しなければならないことだってあり得る。
そのような状況で、今までのようなやり方では不都合が多い。

そこでPHPのようなオブジェクト指向言語で用いられる技術が ORM(Object/Relational Mapping) だ。
この技術を用いることで、データベースのテーブルに存在しているデータ、値をPHPのオブジェクトに変換したり、その逆の変換も行えるようになる。
その上、PHP側で用意したクラスを操作するだけで、それに対応するデータベースのテーブルを自動的に操作できるようになるという。

Laravelでは Eloquent というORMが利用できるため早速使用してみる。

最初にテーブルに対応するモデルと呼ばれるクラスの作成を行う。
ターミナルでプロジェクトのトップディレクトリに移動し、下記コマンドを実行。
自分はどのテーブルを扱うのかわかりやすいよう、テーブル: sampleに対し、モデル: Sampleを作成した。

 $ php artisan make:model Sample(モデル名)

上記のコマンドが成功すると、(プロジェクト名)\appSample(モデル名).phpが作成されているので、開いて確認してみると次のような内容になっている。

(プロジェクト名)\app\Sample(モデル名).php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Sample extends Model
{
    //
}

//とコメントアウトにされている箇所があるが、そこを protected $table = 'sample'; に変更して、使用するテーブル名を定義する。
protectedを付けることで、そのクラス自身と継承したクラス限定でアクセスが可能となるため、テーブルを保護するためにも欠かさずに付与する。
モデルの作成はこれで完了。コントローラーの作成に移る。

(プロジェクト名)\app\Http\ControllersORMController.phpを作成し、下記の内容を入力する。
データベースとの連携で使用した表示用のファイル(プロジェクト名)\resources\views\database.phpを使用するため、view()関数の第一引数はdatabaseにする。

(プロジェクト名)\app\Http\Controllers\ORMController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Sample; 
use App\Http\Requests;
use App\Http\Controllers\Controller;

class ORMController extends Controller
{
    public function getIndex(Request $request)
    {
        $data = Sample::all();
        return view('database', ['message' => 'データベース名: sample','data' => $data]);
    }
}
?>

use App\Sample; で今回作成したモデルを使用することを宣言。
Sample::all() では、Sampleクラスでモデル: Sampleに対応しているテーブル: sampleの全データをallメソッドを使用して取得している。

一回ここで、モデルなどが問題なく使えているか確かめる。
(プロジェクト名)\routes\web.phpにルーティング処理を実装し、実際にページを確認してみる。

web.php
Route::get('/orm','ORMController@getindex');

http://127.0.0.1:8000/orm にアクセスしてみて、http://127.0.0.1:8000/database と同じ内容が表示されているか確かめる。
screenshot.jpg

all の他にもデータベースにおける処理に関するメソッドがモデルクラスには用意されている。
その中の一つが検索機能の where だ。
whereモデル名::where(カラム名, 取得したい値) でテーブルに検索をかけて、 get() でその結果を取得する。
コントローラーに実装して、試してみるとそれぞれ次のようになる。

ORMController.php
class ORMController extends Controller
{
    public function getIndex(Request $request)
    {
        $data = Sample::where('id', 2)->get();
        return view('database', ['message' => 'データベース名: sample','data' => $data]);
    }
}
?>

screenshot.jpg

検索を実行できるwhereには様々な使い方がある。
>likeといった演算子を使いたい場合は where(カラム名, 演算子, 取得したい値)and条件なら where(条件1) -> where(条件2)or条件なら where(条件1) -> orWhere(条件2) という風になる。
データベースから値を取得する機会は多いので、少なくともこれらだけでも覚えておきたい。

Laravelからデータベースのデータを操作する

CRUD処理 において、Readに関してはORMwhereで触れたが、CreateUpdate,Deleteがまだなので、今回はそれらに触れていく。

前準備

ORMの際に作成したモデルに2つのコードを追加する。

(プロジェクト名)\app\Sample(モデル名).php
class Sample extends Model
{
    protected $table = 'sample';

    protected $guarded = array('id');

    public $timestamps = false;
}

protectedはテーブルだけでなく、データも保護できる。
protected $guarded = array('id');主キーを保護する。
更新や削除の際にprotectedで保護されていないと、連携しているテーブルにも影響を与えてしまう。
それらを防ぐためにもテーブルや主キーには protectedで忘れずに保護をかける。

Laravelでデータベースにデータを作成、追加する際、create_at,update_atという、それぞれ作成日時と更新日時を示す項目が自動的に作成される。
今回利用するテーブルではこれらの項目を設けていないため、何もしない状態でデータベースにデータを作成、追加してしまうと、エラーの原因となる。
それを防ぐのが public $timestamps = false; の部分。
このコードを追加することで、データベースにデータを作成、追加する際にcreate_at,update_atが生成されなくなる。
データベースにデータを作成、追加する処理をLaravelに実装する際は、このコードを追加するかテーブルにcreate_at,update_atカラムの作成を忘れないように。

Create

表示用のページとして(プロジェクト名)\resources\views\create.blade.phpを作成し、次のような内容に。

(プロジェクト名)\resources\views\create.blade.php
<!DOCTYPE HTML>
<html>
<head>
    <title>CRUD</title>
    <style>
    body{color:gray; }
    h1{font-size:18pt; font-weight:bold;}
    th{color:white; background:#999;}
    td{color:black; background:#eee; padding:5px 10px;}
    </style>
</head>
<body>
    <h1>LaravelからのCRUD処理</h1>
    <p>{{ $message }}</p>
    <table>
            <tr>
                <th>ID</th>
                <th>NAME</th>
                <th>PASS</th>
                <th>CREATE_DAY</th>
            </tr>
            @foreach($data as $value)
            <tr>
                <td>{{ $value -> id }}</td>
                <td>{{ $value -> name }}</td>
                <td>{{ $value -> pass }}</td>
                <td>{{ $value -> create_day }}</td>
            </tr>
            @endforeach
    </table>
    <table>
    <form method="post" action="/create">
        {{ csrf_field() }}
        <tr><td>NAME:</td><td><input type="text" name="name"></td></tr>
        <tr><td>PASS:</td><td><input type="text" name="pass"></td></tr>
        <tr><td>CREATE_DAY:</td><td><input type="text" name="day"></td></tr>
        <tr><td></td><td><input type="submit"></td></tr>
    </form>
    </table>
</body>
</html>

大まかな内容としては、テーブルのデータを表示して、postで作成するデータを送信するというもの。

IDに値する内容を入力するフォームを作成していないが、これはデータベースのAUTO_INCREMENTを利用しているため。
AUTO_INCREMENTとは他のデータが挿入された際に、適用しているカラムに自動でデータを入力してくれるデータベースの機能。
この機能を適用させるには、phpMyAdminを開いて、AUTO_INCREMENTを適用したいカラムを選択して、変更をクリック。
screenshot.jpg

その後、A_Iの下のチェックボックスにチェックを入れ、保存。
screenshot.jpg

もし、使用しない場合はIDの値を入力するフォームを作成を忘れないように。

postから投げられたデータはコントローラーでデータベースのテーブルに挿入する。
その処理は以下のような内容。

(プロジェクト名)\app\Http\Controllers\CreateController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

use DB; 
use App\Sample; 
use App\Http\Requests;
use App\Http\Controllers\Controller;

class CreateController extends Controller
{
    public function getIndex(Request $request)
    {
        $data = DB::select('select * from sample');
        return view('create', ['message' => 'Create処理を実行する','data' => $data]);
    }

    public function postIndex(Request $request)
    {
        $name = $request -> input('name');
        $pass = $request -> input('pass');
        $day = $request -> input('day');
        $data = array(
            'name' => $name,
            'pass' => $pass,
            'create_day' => $day
        );
        Sample::create($data);
        return redirect()->action('CreateController@getindex');
    }
}
?>

データベースのテーブルにデータを作成する処理、Createではモデル名::create()メソッドを使用する。

getIndex() はデータベースの連携の際の内容と同じなので説明を省略。

postIndex()postが実行されたときに行われる処理を示している。
inputに入力されたname,pass,day$dataという連想配列に格納して、モデル名::create($data)$dataの内容をデータベースのテーブルに格納。
その後、getIndex()にリダイレクトするという流れになっている。

最後に(プロジェクト名)\routes\web.phpに下記のルーティング処理を実装し、実際にページを確認してみる。

(プロジェクト名)\routes\web.php
Route::get('/create','CreateController@getindex');
Route::post('/create','CreateController@postindex');

post
screenshot.jpg

post後(IDの5,6が抜けてるのは作成中にエラーの対応をしていたため)
screenshot.jpg

Update

表示用のページとして(プロジェクト名)\resources\views\update.blade.phpを作成し、form以下を次の内容に。
それ以外はCreateで作成したcreate.blade.phpと同じ。

(プロジェクト名)\resources\views\update.blade.php
    <form method="post" action="update">
        {{ csrf_field() }}
        <tr>
            <td>ID:</td>
            <td>
                {{ optional($data)->id }}
                <input type="hidden" name="id" value="{{ optional($data)->id }}">
            </td>
        </tr>
        <tr>
            <td>NAME:</td>
            <td><input type="text" name="name" value="{{ optional($data)->name }}"></td>
        </tr>
        <tr>
            <td>PASS:</td>
            <td><input type="text" name="pass" value="{{ optional($data)->pass }}"></td>
        </tr>
        <tr>
            <td>CREATE_DAY:</td>
            <td><input type="text" name="day" value="{{ optional($data)->create_day }}"></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit"></td>
        </tr>
    </form>

id以外の各カラムの更新内容を入力するフォームを作成。

optional()引数に指定したオブジェクトにアクセスしたり、そのプロパティを取得する 関数。
オブジェクトがnullの時はnullを返す。

postから投げられたデータをデータベースに更新する処理を記述するコントローラーのclass以下は次のような内容。
classより上はCreateで作成したcreate.blade.phpと同じ。

(プロジェクト名)\app\Http\Controllers\UpdateController.php
class UpdateController extends Controller
{
    public function getIndex(Request $request)
    {
        $id = $request -> id;
        $table = DB::select('select * from sample');
        $data = Sample::find($id);
        $msg = 'Update  Sample Table [id = ' . $id . ']';
        return view('update', ['message' => $msg,'Table' => $table,'data' => $data]);
    }

    public function postIndex(Request $request)
    {
        $id = $request -> input('id');
        $data = Sample::find($id);
        $data -> name = $request -> input('name');
        $data -> pass = $request -> input('pass');
        $data -> create_day = $request -> input('day');
        $data->save();
        return redirect()->action('UpdateController@getindex');
    }
}

$id = $request -> id;URLにおけるクエリパラメータ(~/~?id=123などの?以降の部分)を取得している。
そこで取得した$idモデル名::find() の引数に使用することで、引数に一致するレコードをデータベースから取得し、$dataなどのインスタンスに格納できるようにしている。
データベースのテーブルにおけるデータを更新する際は save() メソッドを使用する。

(プロジェクト名)\routes\web.phpに下記のルーティング処理を実装し、実際にページを確認してみる。

(プロジェクト名)\routes\web.php
Route::get('/update','UpdateController@getindex');
Route::post('/update','UpdateController@postindex');

post前: http://127.0.0.1:8000/update?id=7
フォームの内容を変更したい内容に変えてある。
screenshot.jpg

post
screenshot.jpg

Delete

表示用のページとして(プロジェクト名)\resources\views\delete.blade.phpを作成し、formを次の内容に。
それ以外はCreateで作成したcreate.blade.phpと同じ。

(プロジェクト名)\resources\views\delete.blade.php
    <form method="post" action="update">
        {{ csrf_field() }}
        <tr>
            <td>削除したいレコードのID:</td>
            <td>
                <input type="text" name="id" value="{{ optional($data)->id }}">
            </td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit"></td>
        </tr>
    </form>

削除したいレコードのIDを入力するフォームと送信ボタンだけでOK。

指定されたレコードをデータベースから削除する処理を記述するDeleteController.phpは次のような内容。
classより上はCreateで作成したcreate.blade.phpと同じ。

(プロジェクト名)\app\Http\Controllers\DeleteController.php
class DeleteController extends Controller
{
    public function getIndex(Request $request)
    {
        $data = DB::select('select * from sample');
        return view('update', ['message' => 'レコードの削除','data' => $data]);
    }

    public function postIndex(Request $request)
    {
        $id = $request -> input('id');
        $data = Sample::find($id);
        $data -> delete();
        return redirect()->action('DeleteController@getindex');
    }
}

Create,Updateで使用したコントローラーの内容がわかっていれば $data -> delete(); 以外説明することもない。
$data -> delete();Updateで使用したsave()メソッドと同じような使い方をして、そのデータを削除する使い方をするものだという覚え方で問題はない。

いつも通り、(プロジェクト名)\routes\web.phpに下記のルーティング処理を実装し、実際にページを確認してみる。

(プロジェクト名)\routes\web.php
Route::get('/delete','DeleteController@getindex');
Route::post('/delete','DeleteController@postindex');

post前: http://127.0.0.1:8000/delete
screenshot.jpg

post
screenshot.jpg

番外編: バリデーション

CRUD処理の番外編としてバリデーションにも触れていく。
この機能は入力内容をチェックし、設定した条件を満たしているかどうかを判断するもので実装する方法は複数あるが、今回はフォームのバリデーションを実装する。
フォームのバリデーションはLaravelにおいて、フォームリクエストと呼ばれる。
今回はUpdateで使用したファイル達に、このフォームリクエストを適用していく。

フォームリクエストは以下のコマンドをターミナルで実行して作成する。

$ php artisan make:request UpdateRequest(作成したいファイル名)

処理が完了すると(プロジェクト名)\app\Http\RequestsUpdateRequest(作成したいファイル名).phpが作成される。
UpdateRequest(作成したいファイル名).phpの各メソッドを次のように変更する。

(プロジェクト名)\app\Http\Requests\UpdateRequest(作成したいファイル名).php
public function authorize()
    {
        return true;
    }

public function rules()
    {
        return [
            'name' => 'required|string|max:10',
            'pass' => 'required',
            'day' => 'required|string|max:10',
        ];
    }

バリデーションする条件の指定は、このように連想配列の形をとる。
どのような条件が指定できるかは公式ドキュメントに記載されているため、必要があれば確認を。

そしたら、(プロジェクト名)\app\Http\Controllers\UpdateControllers.phpに作成したバリデーションを適用させていく。

(プロジェクト名)\app\Http\Controllers\UpdateControllers.php
use App\Http\Requests;
use App\Http\Requests\UpdateRequest;

// 中略 

    public function postIndex(UpdateRequest $request)
    {

変更といっても、useの箇所に use App\Http\Requests\UpdateRequest; を追加、public function postIndex(Request $request)public function postIndex(UpdateRequest $request) に変えるだけ。

これで http://127.0.0.1:8000/update?id=4 にアクセスして、フォームの中を全て空にして送信を押すと、最初の状態に戻り、バリデーションが効いていることがわかる。

ただ、エラーメッセージもなしに画面が遷移するのはわかりにくいので、表示用のページである(プロジェクト名)\resources\views\update.blade.phpも変更する。

(プロジェクト名)\resources\views\update.blade.php
// 略

    .alert{color:red;}
    </style>
</head>

// 中略


 </table>
    @if ($errors->any())
        <div class="alert">
          <ul>
              @foreach ($errors->all() as $error)
                  <li>{{ $error }}</li>
              @endforeach
          </ul>
        </div>
    @endif
</body>

これでバリデーションが適用された際、どのフォームが条件を満たしていないかが赤字で表示されるようになった。
image.png

Laravelからテーブルを操作する

ここまででLaravel側からデータベースのテーブルに対する操作ができることを学んだが、Laravelではテーブルそのものを作成することもできる。

ここからはLaravel入門 - 使い方チュートリアル - - QiitaLaravel5 チュートリアル ブログもどきを作る(1) DB設定・マイグレーション - Qiitaシリーズを軸に進めていく。

マイグレーション

Laravelのテーブルそのものを作成する仕組み、それが マイグレーション だ。

まずはデータベースにテーブルを作成するためのマイグレーションファイルを作成するところから始まる。
今回はlaravelsというテーブル名で、それを作成するためのマイグレーションファイルを以下のコマンドをターミナルに入力して作成する。
テーブル名の最後にsを忘れないように

$ php artisan make:migration laravels(作成したいマイグレーションファイル名) --create=laravels(作成したいテーブル名)

そうすると、(プロジェクト名)\database\migrations2020_08_06_095118_laravels(作成したいマイグレーションファイル名).phpが作成される。

(プロジェクト名)\database\migrations\2020_08_06_095118_laravels(作成したいマイグレーションファイル名).php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class Laravels extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('laravels', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('laravels');
    }
}

public function up() にはテーブルを作成するときの処理が、public function down() にはテーブルを削除する処理が記載されている。
このまま、このファイルを実行してもテーブルを作成してもよいが、次のように少しカラムの設定を宣言してみる。

(プロジェクト名)\database\migrations\2020_08_06_095118_laravels(作成したいマイグレーションファイル名).php
    public function up()
    {
        Schema::create('laravels', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name', 10);
            $table->timestamps();
        });
    }

この状態でmigrationが実行されると、次の設定が反映されたカラムになる。

カラム名 設定 
id AUTO_INCREMENT
name VARCHAR(10)
create_at timestamp
update_at timestamp

上でも説明したように、create_atupdate_atのカラムは自動で作成され、AUTO_INCREMENTは自動挿入、timestampは日時が自動で挿入される機能。

では、このファイルを実行して、テーブルを作成してみる。

ターミナルに次のコマンドを入力することで、マイグレーションファイルが実行される。

$ php artisan migrate

phpMyAdminで確認してみる。
image.png

migrateテーブルを作成するpublic function up()の処理が行われた。
public function down()の処理はmigrate:rollbackmigrate:resetで実行する。

migrate:rollback最後に行ったマイグレーション操作を取り消す
migrate:reset全てのマイグレーションを削除する

シーディング

シーディング はテーブルの初期化やテストデータとなるレコードを挿入するLaravelの仕組み。
今回はこの仕組みを利用して、マイグレーションで作成したmigrationテーブルを初期化(最初のデータを挿入)する。

シーディングマイグレーション同様、ファイルの作成から始めるが、その前にマイグレーションで作成したテーブルに対応するモデルの作成を行う。

$ php artisan make:model laravel(テーブル名の最後のs抜き)

マイグレーションの時のテーブル名における最後の文字にsを付けてもらった理由がここにある。
モデル名はテーブル名の単数形にすることで自動的にテーブルを判断する機能がある。
テーブル名: 複数形モデル名: 単数形の命名規則を忘れないように。

ファイルを編集する必要はないので(プロジェクト名)\app\laravel(テーブル名の最後のs抜き).phpがあるのを確認すればOK。

その後、ターミナルで下記のコマンドを実行。

$ php artisan make:seeder StudySeeder(作成したいシーディングファイル名)

そうすると、(プロジェクト名)\database\seedsStudySeeder(作成したいシーディングファイル名).phpが作成される。

(プロジェクト名)\database\seeds\StudySeeder(作成したいシーディングファイル名).php
<?php

use Illuminate\Database\Seeder;

class StudySeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        //
    }
}

migrationテーブルに挿入するデータは下の表。

id name create_at update_at
1 Laravel 作成日時 作成日時
2 Migration 作成日時 作成日時
3 Seeding 作成日時 作成日時

StudySeeder(作成したいシーディングファイル名).phpで、この表の内容を入力できるようにするには次の内容にする。

(プロジェクト名)\database\seeds\StudySeeder(作成したいシーディングファイル名).php
public function run()
    {
        DB::table('laravels')->truncate();

        $table_data = [
            ['name' => 'Laravel'],
            ['name' => 'Migration'],
                ['name' => 'Seeding']
        ];

        foreach($table_data as $data) {
            \App\laravel::create($data);
        }
    }

DB::table('laravels')->truncate(); でシーディングの対象となるテーブルの指定と初期化。
$migrationdata~ には連想配列の形式で入力したいレコードのデータを挿入。id,create_at,update_atは自動挿入されるようにテーブル側で設定されているため、nameカラムだけ指定。
foreach($table_data as $data)~ で挿入するデータを一つずつテーブルに挿入していく。
\App\laravel::create();laravel部分は各自のモデル名に。

シーディングではコマンド実行時に同じディレクトリにあるDatabaseSeeder.phpを実行するため、このファイルも編集する。

(プロジェクト名)\database\seeds\DatabaseSeeder.php
public function run()
    {
        $this->call(StudySeeder::class);
    }

StudySeederの部分を各自のシーディングファイル名に変更。

以上で設定は終わりなのでシーディングを実行してみる。
シーディングの実行はターミナルで次のコマンドを叩く。

$ php artisan db:seed

phpMyAdminを確認してみる。
image.png

Laravelシステム作成手順

今回の学習を踏まえてLaravelの作成手順をまとめる。

0. (プロジェクト名)\.envにデータベース設定を記述
1. マイグレーションでテーブルを作成
2. シーディングでテーブルにデータ挿入
3. (プロジェクト名)\resources\views\に表示用のファイル、bladeテンプレートを使用したファイルを作成
4. (プロジェクト名)\app\Http\Controllers\に3で作成したファイルへデータなどを渡すコントローラーを作成
5. バリデーション作成
6. (プロジェクト名)\routes\web.phpでルーティング処理

おわりに

今回はLaravelで簡易的なサイトが作れるようになるまでの基礎を学んだ。
Laravelにはもっと多くの機能があり、この記事では触れられていないことが多い。
下記の参考資料などを参考に学習を進めていき、自分が作成したいサイトやシステムをLaravelで実装できるようにしていく。

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FuelPHPでSQLの最新のレコードを取得する

SQLにあるレコードの、created_atが最も新しいものを取得したいといったとき、
SELECTにMAX(created_at)とするだけでやってくれます。

FuelPHPの場合は

example.php
$query = \DB::select('id', \DB::expr('MAX(created_at)'))
->from('table_name')
->execute();

とするだけでできます。
これに加えて
group_by('id')
などを使うときにすごく役立ちます。

配列で取得したあとに頑張って新しい配列に入れるみたいなことをせずに済みます。(自戒)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

使用してきた関数(メソッド)を忘れないように記載していくよ(PHP版 随時更新)

はじめに

今まで使用してきたメソッド
これから使用してみるメソッド
忘れないように、随時更新していきます。

時間の都合上、
コードは載せる場合と、
載せない場合があります。

例)
▶︎ メソッド

意味:

参考コード

雛形

一つ一つ書くのが手間なので、
雛形を書かせていただきました!

書いていきます٩( ᐛ )و

▶︎ is_null

意味:nullであればtrue,それ以外であればfalseを返す

参考コード

<?php

$value = NULL;

if (is_null($value)){
  echo 'NULLです。';
}else{
  echo 'NULLではありません。';
}

実行結果
//NULLです。
?>

▶︎ asset

意味:「publicディレクトリ」のパスを返すヘルパ関数
    ヘルパとはviewファイルで使えるメソッドのことです。
    現在のURLのスキーマ(httpかhttps)を使い、
    アセットへのURLを生成するメソッド

参考コード

<img src="{{ asset('storage/image/' . $headline->image_path) }}">
//保存した画像のファイル名が $head->image_pathに入っていることを想定
//このことで「.」を使用して、画像のパスを返していることになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelの学習備忘録 その3

プログラマー初心者です。
Laravelを触って頑張っています。
Laravelでアプリを作りながら学んだことを雑多に記していきます。
記事の正確性は低めです。なので、各項目ごとの参考を参照したほうが良いと思います。
それでは!

バリデーション

参考:ドキュメント「バリデーション」
Laravelではバリデーションの方法がいくつかあるようだ。
- validate()メソッドを使う
- FormRequestを使う←おすすめとのこと。

FormRequestを使う方法は、学習済み。

validateメソッドの方法でも試してみよう。
やりかたは、コントローラの中で、$requestを受けるときに、
$validated_date = $request->validate([バリデーションルールを記述]);
でオッケー。簡単。

でも、FormRequestを使おう。なぜなら、コントローラーをすっきりさせて、見通しを良くしたいから。
また、より複雑なロジックを組める。

$fillableか$guardedか

参考:Qiita「僕がLaravelのEloquentに$fillableでなく$guardedを指定する理由」
\$fillableで指定したもののみ代入可能(ホワイトリスト化)。
\$guardedは指定したものが代入不可能(ブラックリスト化)。
記事の内容が僕にはまだ理解しづらかったので、とりあえず今はfillableをつかう。

中間テーブル

参考:Qiita「中間テーブルとは」
- 多対多のテーブル同士のリレーションだと、お互い関係させようとすると、カラムが無駄に多くなったり、空白になってしまう。
- 例:articlesテーブルとtagsテーブルがあるときに、例えば、articlesタグにtagカラムをつけようとすると、1つの記事にタグをつけたいけど、いくつつくかわからないし、tagカラムがいっぱい作っておかないといけないし、空白フィールドもできて管理しづらくなる。
そこで間に1つテーブル入れることで、空白のカラムをつくることなく、管理しやすくなる。詳しくは上の参考を見るのが一番いい。
- なお、laravelで中間テーブルのマイグレーションを作るときは、単数形で!

マイグレーションでのつまづき

複数形

テーブル、マイグレーション作成時に複数形にするとLaravelではきちんと関係性を読み込んでくれるのだが、複数形にするときに困ったのが、'diary'の複数形。
'diarys' or 'diaries' どっちだ。

答えはdiaries

'diarys'で最初作ったら、マイグレートのときにエラーが出て先に進めなかった。やり直して、diariesでいったら、通った。単数形にsをつけるだけじゃないんだね。英語の初歩って感じだけど、複数形の法則ってちゃんとデータベースに仕組みが入っているのね。

マイグレーションファイルの作成順番

外部キー制約させたい元のテーブルのマイグレーションファイルは先に作っておかないと、エラーがでるので、マイグレーションの順番にもちょっと注意が必要。
よく考えてみれば、日付が新しい順から実行してマイグレーションupするんだから、「外部キーがないよ」ってなるのは当たり前か。
一気にマイグレーションファイルを作ってからマイグレートでいいけど、ファイルの作成順番が大事ということで。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[個人メモ][PHP]ドメインのSSL証明書の有効期限の取得(subjectAltNameまで見る)

個人メモとして記事っぽくまとめ。

PHPでドメインのSSL証明書の有効期限を取得するコードは先人たちの英知に学ぶとして大幅に前段を省略しますが、最後はopenssl_x509_parseで返ってきた値の[subject][CN]と対象ドメインを比較して有効期限の取得判断をしています。
この時面倒なのが、例えばwww.yahoo.co.jpでこれを調べてみると、

image.png

のように*付きでくるので単純比較では不一致になってしまうことですね。
まぁこれは正規表現でも使ってあげればいいので、脳死コーディングするなら

if( strpos($parsed['subject']['CN'], $domain_name) !== false ||
    preg_match(sprintf('/^.%s$/',$parsed['subject']['CN']), $domain_name) === 1 )
{
    echo '有効期限:' . date('Y/m/d', $parsed['validTo_time_t']);
}

とでもしてあげればよいのですが、面倒なものだと、

image.png

とかあるわけですね。
そんなときは、[extensions]の[subjectAltName]まで見てあげないといけないはずです。

image.png

うわっめんどい、と一瞬思いますが、単純なパース処理なので、難しく考えずexplode、foreach、preg_matchあたりを使えばできるかと。余計な空白もあるのでtrimも忘れずに。

まぁここまで厳密に有効期限チェックするような監視よろしくなバリバリの仕組みでなければ、せいぜい*あたりだけ見てあげればいい気はしますね。知らんけど。

とここで終わるのもなんなので、

$arrDomain = [
    'qiita.com',
    'www.youtube.com',
    'www.google.co.jp',
    'www.yahoo.co.jp',
];

foreach($arrDomain as $domain_name) {
    echo '■' . $domain_name . PHP_EOL;

    $stream_context = stream_context_create(array(
        'ssl' => array('capture_peer_cert' => true)
    ));

    $resource = stream_socket_client(
        'ssl://' . $domain_name . ':443',
        $errno,
        $errstr,
        30,
        STREAM_CLIENT_CONNECT,
        $stream_context
    );

    $cont = stream_context_get_params($resource);
    $parsed = openssl_x509_parse($cont['options']['ssl']['peer_certificate']);

    $func_chk_domain = function($cn, $domain) {
        return (strpos($cn, $domain) !== false) ||
            (preg_match(sprintf('/^.%s$/',$cn), $domain) === 1);
    };

    // 単純にCNで比較(完全一致と*時の正規表現)
    $check_flg = false;
    if( $func_chk_domain($parsed['subject']['CN'], $domain_name) ) {
        echo 'CNで一致' . PHP_EOL;
        $check_flg = true;
    }
    else {
        // CNで一致しない場合はextensionsのsubjectAltNameを精査
        $arrDNS = explode(',', $parsed['extensions']['subjectAltName']);

        foreach($arrDNS as $dns) {
            if( $func_chk_domain(str_ireplace(['DNS:',' '],'',$dns), $domain_name) ) {
                echo 'subjectAltNameで一致' . PHP_EOL;
                $check_flg = true;
                break;
            }
        }
    }

    if( $check_flg ) {
        echo '有効期限:' . date('Y/m/d', $parsed['validTo_time_t']) . PHP_EOL;
    }
    else {
        echo '取得NG' . PHP_EOL;
    }
}

で取れました。

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MediaWikiのAPIを使ってページを自動作成する

筆者は、MediaWikiを自分のための情報収集と整理に使っています。いわば、個人のナレッジマネジメントですが、そのときに重宝しているのが、MediaWikiのRESTfulなAPIを使用するクライアントアプリケーションです。タイトルとページコンテンツを与えて新規ページを作成するという至って簡単なものですが、その割には役にたっています。記述言語はPHPです。

最近ではAPIを使ったCMS管理が必須になっていますが、APIを使ったクライアントアプリケーションの作成は簡単だよ、という例です。

ソースはGitHubにあげています。MediaWikiシステムをプライベートに利用している方は(あまり多くないとは思いますが)、カスタマイズしてそれぞれの用途で利用してください。

週別のテンプレートとは

筆者が最も利用しているのが、図1ようなテンプレートです。これは2020年29週の分ですが、週毎の月日と曜日、リンクをつけただけの簡単なものです。

リスト1「2020年29週」
[[週報:2020年28週|前週]] --- [[週報:2020年30週|次週]]
== 7/13(月) ==
== 7/14(火) ==
== 7/15(水) ==
== 7/16(木) ==
== 7/17(金) ==
== 7/18(土) ==
== 7/19(日) ==
<hr />
[[週報:2020年28週|前週]] --- [[週報:2020年30週|次週]]

リスト1のテンプレートは、実際には図1のように表示されます(月曜日の欄には既にリンクメモが付加されています)。こんな具合に、思いついたメモや当日得た情報をその日の欄に順不動で追加して行きます。

図1
図1 リスト1に対するMediaWikiの表示画面の一部。メニュー欄などは省略

MediaWikiの機能として上部に目次が追加されます。各小見出しにはid属性が付与されますので、外部からリンクすることが可能です。この辺りの基本機能はQiitaとほとんど同じです。

「編集」という青のリンクは、見出し以下の編集機能です。CMSの仲間であるQiitaと違って、各小見出し以下だけを(次の同レベル以上の小見出しまで1)編集できるのが便利です。Qiitaは全文を編集しなければならないので長い記事は書きにくいのですが、MediaWikiは適当に小見出しを入れることにより編集画面を小さくできます。

最初は、リスト1を手書きで追加していたのですが、日付や曜日を調べるのが意外に面倒です。週番号を間違えたりすると、ややこしいことになってしまいます。そこで、テンプレートを自動作成することにしました。

必要条件

以下のアプリケーションを実行するためには

  • MediaWikiのデフォルト認証を使っている(PluggableAuthを使用していない)
  • 使用するユーザIDにページ作成権限がある

ことが必要です。PluggableAuthによる認証を使用している場合は、OAuth認証が必要になります。MediaWikiのAPIにおけるOAuth認証については、別記事にする予定です。

MediaWikiのAPI

MeidaWikiのAPIのドキュメントは、公式サイトのAPI:Mainページ以下にあります2。コマンド名に相当するactionプロパティだけで120種類を越える膨大なAPI群です。但し、ここで使うactionは、

  • query
  • edit
  • clientlogin

の3種類だけですので、とても簡単です。

処理の流れ

全体的な処理の流れは図2のようになります。PHPの起動スクリプトはローカルサーバ上に置きます。ページがなければ自動的に作成します。最後にリダイレクトしますので、このURL(図2ではlocalhostの8081番ポートにアクセスしています)にアクセスすれば常にその週のページが表示されるので便利です。筆者はブラウザのホーム画面にしています。

図2
図2 処理の流れ

PHPの起動スクリプトをリスト2に示します(例外処理等は省略しています。詳しくはGitHubをみてください)。経験のある方なら、クラスの名前だけで何をしているかおわかりになるでしょう。

リスト2
011: // パラメータを得る
012: $config = require_once 'config/ClientConfig.php';
013: // get()とpost()の実装
014: $client = new MwCurlClient($config);
015: // RESTfulなAPIに対するエージェント
016: $agent = new MwAgent($client);
017: // ログイン
018: $agent->clientLogin(
019:    $config['username'],
020:    $config['password'],
021:    $config['url']
022: );
023: // $diff=0は今週をあらわす
024: $diff = isset($_GET['diff'])?$_GET['diff']:0;
025: // タイトルは「20XX年:YY週」,YY=今週の週番号
026: $title = MwWeeklyReport::getTitle($diff);
027: if ($agent->existsPage($title) == false) {
028:    // ページが無いなら、あらかじめ作成する
029:    // 週番号に対するテンプレートを得る
030:    $text = MwWeeklyReport::getBlankPage($diff);
031:    $agent->createNewPage($title, $text);
032: }
033: // 「20XX年:YY週」のページにリダイレクト
034: header('Location: ' . $agent->getUrl($config['url'], $title));

リスト2に登場するオブジェクトを次にあげます。

  1. ClientConfig: usernameなどのパラメータを格納する配列
  2. MwCurlClient: PHPのcurl関数群を使用してget()とpost()を実装する
  3. MwAgent: MediaWikiのAPIにアクセスする各種のメソッド
  4. MwWeeklyReport: リスト1でコンテンツマネージャと記したクラス。ページタイトルとテンプレートを作成する

以下では、MwAgentクラスの関連するメソッドについて説明します。

clientLogin()

MwAgentクラスのclientLoginメソッドをリスト3に示します。

「action=clientlogin」に必要なpostパラメータについてはclientloginの解説を参照してください。パラメータのうち「logintoken」は、事前に取得しておく必要があります3

実際には、ここで指定したパラメータに加えて「format=json」を指定する必要がありますが4、post()の実装のなかで付加していますので、リスト3で指定していません。

リスト3
101:    public function clientLogin($user, $password, $rootUrl) {
102:        $token = $this->getLoginToken();
103:        $result = $this->client->post(array(
104:            'action' => 'clientlogin',
105:            'loginreturnurl' => $rootUrl,
106:            'username' => $user,
107:            'password' => $password,
108:            'logintoken' => $token
109:        ));
110:        $status = $result['clientlogin']['status'];
111:        if ($status !== 'PASS') {
112:            throw new Exception("'clientlogin' failed. STATUS = " . $status);
113:        };
114:    }
getLoginToken()

clientloginに必要なログイン用トークンはリスト4のように取得します。「action=query」はデータを取得するためのAPIです。指定するパラメータにより様々なデータが取得できます。「meta=tokens」は、セキュリティー上の理由で必要なトークンを取得するのため使用します。

clientLogin()と同様、ここで指定したパラメータに加えて「format=json」を指定する必要がありますが4、get()の実装のなかで付加していますので、リスト4で指定していません。以下も同様です。

リスト4
201:    protected function getLoginToken() {
202:        $result = $this->client->get(array(
203:            'action' => 'query',
204:            'meta' => 'tokens',
205:            'type' => 'login'
206:        ));
207:        return $result['query']['tokens']['logintoken'];
208:    }
existsPage()

指定したタイトルを持つページが存在するかどうかを調べるだけのものです。これも「action=query」を使う問い合わせです。

リスト5
301:    public function existsPage($title) {
302:        $result = $this->client->get(array(
303:            'action' => 'query',
304:            'titles' => $title
305:        ));
306:        $pages = $result['query']['pages'];
307:        $id = array_keys($pages)[0];
308:        return $id >= 0;
309:    }
createNewPage()

action=edit」を用いた操作です。

コンテンツマネージャ(MwWeeklyReport)から得たテンプレートを内容として、新たなページを作成します。ここではページ作成と呼んでいますが、編集の場合も全く同じ手順です(と言っても、編集操作はMediaWiki画面で行うのが通常です)。getCsrfToken()は、getLoginToken()から「type=login」を除いただけですので、省略します。

これまでの例では、get()でもpost()でも良かったのですが5、「action=edit」だけはpost()を使う必要があります。

リスト6
401:    public function createNewPage($title, $text) {
402:        $token = $this->getCsrfToken();
403:        $result = $this->client->post(array(
404:            'action' => 'edit',
405:            'token' => $token,
406:            'title' => $title,
407:            'text' => $text
408:        ));
409:        $status = $result['edit']['result'];
410:        if ($status != 'Success') {
411:            // 作成に失敗した
412:            throw new Exception("Failed to create new page.");
413:        }
414:    }

あとがき

APIを使ってCMSにアクセスすることが簡単だと思ってもらえたでしょうか。もし、そうでないとしたら私の説明不足ですので、気軽に質問してください。

なお、ローカルPCの起動スクリプトをサービスとして起動する場合の処理については、「PHPのビルトインウェブサーバをsystemdのサービスとして登録する」という記事にしました。ご参考に。


  1. MediaWikiの「Wiki markup言語」では、見出しを複数の「=」ではさみます。「=」の数が見出しのレベルで、2個の「=」で挟むとhtmlのh2に変換されます。Qiitaで使う「Markdown言語」では前置きの「#」に相当します。 

  2. 残念ながら、日本語ページはあまり内容が伴っていないので、なるべく英語ページを参照してください 

  3. あらかじめトークンを取得する必要があるのは、XSS対策のひとつです 

  4. 最近では、結果をjsonで戻すことは、RESTfulなAPIにおける事実上の標準になっています。MediaWikiでも、「format=json」がデフォルト設定であるとドキュメントに書かれているのですが、v1.34.2では、まだそうはなっていないようです 

  5. clientLogin()でpost()を使っているのは、セキュリティー上の理由です 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelのタスクスケジュールが動かない(24時設定)

昨日まで動いていたのに、今日は動かない・・・
昨日のリリースが悪いのかしら。。でも何も悪いことしていないのに・・・

スケジュール定義を見直そう

スケジュールタスクは全部App\Console\Kernelクラスのscheduleメソッドの中に定義します。
この定義でミスしていました。
毎日24時に実行したいからって

$schedule->command('cms:update-article-point')->dailyAt('24:00');

と書くと落ちます。こんなログを吐いて死にます。

In CronExpression.php line 155:
  Invalid CRON field value 24 at position 1

このように書きましょう。

$schedule->command('cms:update-article-point')->dailyAt('00:00');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PHP] 簡易ログイン機能を実装してみた

はじめに

phpを使用して、簡易的なログイン認証を実装してみました。(技術力低いのでやばいかもしれないが...)

基本的な流れ
1. idとpasswordを入力する。
2. 入力したidとpasswordがデータベースに紐づけされて登録されているかをチェック。
3. 登録されていたらログインし、以外はエラーを表示する。

まぁ簡易的なのでそんなかんじです笑笑

該当ソースコード

1. login.php

ログインするために、ID、パスワードを入力する画面です。

ログイン画面(login.php)
<?php
    require_once("function.php");
    session_start();
    header("Content-type: text/html; charset=utf-8");
?>

<!doctype html>
<html lang="ja">
<head>
    <link rel="stylesheet" type="text/css" href="style1.css">
</head>
<div class="form-wrapper">
    <h1>ログイン</h1>
    <?php
        if (isset($_SESSION["error_status"])) {
            if ($_SESSION["error_status"] == 1) {
                echo "<h2 style='color:red'>IDまたはパスワードが異なります。</h2>";
            }
            if ($_SESSION["error_status"] == 2) {
                echo "<h2 style='color:red'>不正なリクエストです。</h2>";
            }

            //エラー情報のリセット
            $_SESSION["error_status"] = 0;
            }
    ?>

    <form action="login_check.php" method="post">
    <div class="form-item">
        <label for="ID"></label>
        <input type="text" name="id" required="required" placeholder="ID">
    </div>
    <div class="form-item">
        <label for="password"></label>
        <input type="password" name="password" required="required" placeholder="パスワード">
    </div>
    <div class="button-panel">
        <input type="submit" class="button" title="ログイン" value="ログイン">
    </div>
    </form>

</div>
</html>

2. login_check.php

入力したID、パスワードが事前に登録されているユーザのものなのかを認証します。

ログイン認証(login_check.php)
<?php
  require_once("function.php");
  session_start();
  header("Content-type: text/html; charset=utf-8");

  //パラメーター取得
  $id = $_POST['id'];
  $password = $_POST['password'];

  //ログイン判定

  //DB接続
  try {
    /*dbname: test
      host: localhost (DBをローカル環境に置いている)
      username: root
      password: '' (未設定)
    */
    $pdo = new PDO('mysql:dbname=test;host=localhost;charset=utf8mb4', 'root', '', 
    [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]
    );

    $stmt = $pdo->query('SELECT id, password, name FROM user');

    $count = 0;

    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
      $db_id = $row["id"];
      $db_password = $row["password"];
      $db_name = $row["name"];
      $count = $count + 1;

      //ログイン失敗
      if ($count == 0) {
        $_SESSION["error_status"] = 1;
        header("HTTP/1.1 301 Moved Permanently");
        header("Location: login.php"); 
        exit();
      }

      // パスワードチェック
      // パスワードが一致したら「1」を、不一致なら「0」を返す
      if(password_verify($password, $db_password)) {
        $hantei = 1;
      } else {
        $hantei = 0;
      }

      if ($id == $db_id && $hantei == 1) {
          // ログイン成功
          // セッションに格納することで、ログインユーザ情報をログイン先画面で使用することが可能

          //セッション ID の振り直し
          session_regenerate_id(true);

          //セッションに ID を格納
          $_SESSION['id'] = $id;

          //セッションに name を格納
          $_SESSION['name'] = $db_name;

          //CSRF のトークン作成
          $_SESSION["token"] = get_csrf_token();

          //リダイレクト
          header("HTTP/1.1 301 Moved Permanently");
          header("Location: 〇〇.php"); // ログイン先

          exit();
      }
    }
  } catch (PDOException $e) {
    exit('データベース接続失敗。'.$e->getMessage());
  }

  //ログイン失敗
  $_SESSION["error_status"] = 1;
  header("HTTP/1.1 301 Moved Permanently");
  header("Location: login.php"); 

?>

3. function.php

function.php
<?php
//define("DNS","mysql://user01:0000@localhost/Test?charset=utf8");
define("SERVER", "localhost");
define("STRETCH_COUNT", 1000);

/*
* CSRF トークン作成
*/
function get_csrf_token() {
  $TOKEN_LENGTH = 16;  //16*2=32byte
  $bytes = openssl_random_pseudo_bytes($TOKEN_LENGTH);
  return bin2hex($bytes);
}

/*
* パスワードをソルト+ストレッチング 
*/
function strechedPassword($salt, $password){
    $hash_pass = "";

    for ($i = 0; $i < STRETCH_COUNT; $i++){
        $hash_pass  = hash("sha256", ($hash_pass . $salt . $password));
    }

    return $hash_pass;  
}

/*
* ソルトを作成
*/
function get_salt() {
  $TOKEN_LENGTH = 4;//4*2=8byte
  $bytes = openssl_random_pseudo_bytes($TOKEN_LENGTH);
  return bin2hex($bytes);
}

/*
* URL の一時パスワードを作成
*/
function get_url_password() {
  $TOKEN_LENGTH = 16;//16*2=32byte
  $bytes = openssl_random_pseudo_bytes($TOKEN_LENGTH);
  return hash("sha256", $bytes);
}
?>

環境

windows10 64bit
実行環境: xampp (mysql、php、apache等をまとめてインストールでき、インストールも簡単なので)

おわりに

こういう系の記事は初めてなので、見よう見まねで書きました笑笑
抜けている箇所がありましたら、コメント欄で教えていただくとありがたいです。

次回は、ログインアカウントの登録機能について記事を書きたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

連想配列のデフォルト値を省略すると実行速度は速くなる

気になったこと

以下のように引数の連想配列に不足しているキーを自動で補ってくれる関数がある。

f.php
 function f($option = []) {
    $option += [
        'text1' => '1',
        'text2' => '2',
        'separator' => '/',
    ];
    return implode($option['separator'], [$option['text1'], $option['text2']]);
 }

関数の呼び出し時に(a)オプション配列にすべてのキーを指定する場合、(b)デフォルト値を省略した場合、どちらの実行速度が速いか?

時間計測スクリプト

benchmark.php
 $t1 = microtime(true);
 for($i=0;$i<500000;$i++) {
    f([
        'text1' => 'abc',
        'text2' => 'def',
        'separator' => '/',
    ]);
 }
 $t2 = microtime(true);

 $t3 = microtime(true);
 for($i=0;$i<500000;$i++) {
    f([
        'text1' => 'abc',
        'text2' => 'def',
    ]);
 }
 $t4 = microtime(true);

 echo ($t2-$t1) . "\n";
 echo ($t4-$t3) . "\n";
 exit;

結果

条件 実行時間avg (sec)
(a)オプション配列にすべてのキーを指定する場合 0.12881302833557
(b)デフォルト値を省略した場合 0.12693285942078
  • デフォルト値を省略したほうが実行速度は(若干)速い。
  • フレームワーク(CakePHP, Laravelなど)の関数のように中身が複雑だと、さらに差が広がる。
  • デフォルト値は省略したほうが実行速度は速いが、フレームワークのバージョンアップでデフォルト値が変わり、デグレが起きる可能性に留意する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む