20200923のPHPに関する記事は24件です。

引数の渡し方

今日はlaravelにおいてviewファイルへの引数の渡し方について記録していくよ~

TestController.php
  public function test(){
   $data1 = 1;
  $data2 = 2;

   return view('test_view' , ['data1' => $data1 , 'data2' => $data2 ]);
}//1 2

compact関数を使って同じ出力になる書き方:

TestController.php
  public function test(){
   $data1 = 1;
  $data2 = 2;

   return view('test_view' , compact('data1' , 'data2'));
}//1 2

以上だよお~

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

php ファイルのアップロード

up.php
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <title>img_upload</title>
</head>

<body>

  <?php
  var_dump($_FILES['file']);

  $filename =  './img/'.date('Y-m-d H:m:s') . '.jpg';

  if ($_FILES['file']) {
    move_uploaded_file($_FILES['file']['tmp_name'], $filename);

  }
  echo "<img src = '$filename'>"
  ?>
//enctypeは決まり文句
  <form action="up2.php" method="POST" enctype="multipart/form-data">
    <input type="file" name="file" accept="image/*">
    <input type="submit" value="ファイルをアップロードする">
  </form>




</body>

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

PHP Laravel 6 おすすめ映画投稿サイト作成過程 4:投稿機能作成編

ルーティング

登録画面にはcreateを使用します。

| GET|HEAD  | recommends/create           | recommends.create  | App\Http\Controllers\RecommendController@create                        | web          |

DBへの登録にはstoreを使用します。

| POST      | recommends                  | recommends.store   | App\Http\Controllers\RecommendController@store                         | web          |

viewファイルの作成

Screen Shot 2020-09-23 at 20.39.31.png
登録が必要な内容をビューファイルに設定しましょう。
今回は、以下の内容を登録対象とします。
※画像のアップロードに関しては、後日編集します

登録する内容
映画タイトル
映画URL
映画概要
感想

formタグで入力データをrecommends.store へ送る

@csrfを忘れないようにしましょう。※不正な動作を防ぐために記載します。詳細はGoogleで検索してください。

recommend/resources/views/recommends/create.blade.php
<form method="POST" action="{{route('recommends.store')}}">
@csrf
 <table>
   <thead>
     <tr>
       <th>タイトル</th>
       <th>映画URL</th>
       <th>概要</th>
       <th>感想</th>
     </tr>
     <tr>
       <th><input type="text" name='title'></th>
       <th><input type="URL" name='url'></th>
       <th><textarea name="description" id="" cols="30" rows="10"></textarea></th>
       <th><textarea name="Impressions" id="" cols="30" rows="10"></textarea></th>
       </tr>
  </thead>
 </table>
 <button type="submit">登録</button>
 <a href="{{route('recommends.index')}}">一覧に戻る</a>
</form>

レコードの保存処理

レコードの保存には、Modelのクリエイトメソッドを使用します。
登録後はindexページにリダイレクトさせました。

recommend/app/Http/Controllers/RecommendController.php
public function store(Request $request)
    {
        Recommend::create($request->all());
        return redirect()->route('recommends.index');
    }

一方、allで情報を取得した場合、そこには全ての情報が含まれているため、どの情報を登録するかModelで指定する必要があります。

recommend/app/Models/Recommend.php
class Recommend extends Model
{
    protected $fillable = [
      'title',
      'image_file_name',
      'image_title',
      'url',
      'description',
      'Impressions'
    ];
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PDOを接続とプリペアドステートメント説明

PDOの接続方法

僕はmysqliに比べて、分かりづらかったので、PDOの接続の仕方を書いてみました。

<?php
try {
    $pdo = new PDO("mysql:dbname=データベース名; host=localhost; charset=utf8mb4", "ユーザー名", "パスワード");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    echo "接続完了";
} catch (PDOException $e) {
    exit();
    echo "接続失敗";
}
?>

プリペアドステートメントについて

プリペアドステートメントって、初めは分かりづらいですよね。
プリペアドステートメントとは、SQL文を最初に用意しておいて、その後はクエリ内のパラメータの値だけを変更してクエリを実行できる機能のことです。
この機能を利用することでクエリの解析やコンパイル等にかかる時間は最初の一回だけで良くなり、より高速に実行することができます。
また、SQLインジェクション対策に必要なパラメータのエスケープ処理も自動で行ってくれるため、安全かつ効率の良い開発が出来ます。

プリペアドステートメントを利用してクエリを実行する流れ

PDOオブジェクトの作成

prepareメソッドでSQL文をセット

bindValue or bindParamでパラメータに値をセット

executeメソッドでクエリを実行

fetch or fetchAllメソッドで結果を配列で取得

INSERTだとこんな感じで使います。

$stmt = $pdo -> prepare("INSERT INTO blog (title, text) VALUES (:title, :text)");
    $stmt->bindParam(":title", $_POST['title'], PDO::PARAM_STR);
    $stmt->bindParam(":text", $_POST['text'], PDO::PARAM_STR);
    $stmt->execute();

参考にしたサイト
PDO prepare プリペアドステートメントの使い方

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

忙しい人のためのphp.ini設定大全

vpsでやっているとphp.iniの設定で苦しめられることがあるので、
備忘録としてまとめておきます
よかったら参考にしてください

目次

忙しい方のための推奨設定一覧
allow_url_fopen
allow_url_include
default_charset
disable_functions
display_errors
enable_dl
expose_php
file_uploads
log_errors
magic_quotes_gpc
max_execution_time
max_input_time
memory_limit
register_globals
safe_mode
session.cookie_httponly
session.cookie_lifetime
session.cookie_secure
session.entropy_file
session.gc_maxlifetime
session.hash_function
session.use_cookies
session.use_only_cookies
session.use_trains_sid

忙しい方のための推奨設定一覧

設定 推奨値 初期値
allow_url_fopen On On
allow_url_include Off Off
default_charset UTF-8
disable_functions
display_errors Off On
enable_dl Off On
expose_php Off On
file_uploads Off On
log_errors On Off
magic_quotes_gpc Off On
max_execution_time 30 30
max_input_time 60 -1
memory_limit 32MB 128MB
register_globals Off Off
safe_mode On Off
session.cookie_httponly On Off
session.cookie_lifetime 0 0
session.cookie_secure On Off
session.entropy_file /dev/urandom
session.gc_maxlifetime 1440 1440
session.hash_function 1(SHA1) 0(MD5)
session.use_cookies On On
session.use_only_cookies On On
session.use_trains_sid 0 0

allow_url_fopen

Onの場合外部のURLをローカルファイルと同じように読み込むことができる

example.php
file_get_contents('https://satorunooshie.net');

しかしPHP5.2よりも前の場合はincluderequireなどでも外部URLを読み込めるためインクルード攻撃を受ける可能性がある
PHP5.2以降ではallow_url_includeに分けられた
インクルード攻撃は

example.php
include $_GET['text'];

のようにすると攻撃者が自由にサーバー上でコードを実行することができるようになるらしい
またinclude文の引数にユーザーの入力値を含まない場合でも
ファイルの中身がブラウザから確認できる場合、インクルードファイルの流出によって攻撃の手がかりを与えることになるので注意
インクルードファイルをドキュメントルートよりも上に配置すれば大丈夫

allow_url_include

Offのままで良い、理由は前述

default_charset

HTTPヘッダーでの文字エンコードの指定
指定しない場合XSSの危険性があるのでUTF-8に指定する
特定のページのみ変更する場合はini_set()関数かheader()関数を使う

disable_functions

無効にする内部関数の設定
コーディング規約などで利用を禁止している場合や危険な関数を利用しない場合に設定する

php.ini
disable_functions = exec,popen,passthru,system

display_errors

Offに設定する
エラーメッセージはHTMLエスケープされずに出力されるのでXSSが可能なことがあるため

enable_dl

PHPの拡張モジュールをスクリプト内からロードできる
PHP5.3以降では非推奨

expose_php

OnにするとHTTPヘッダーにPHPのバージョンが表示される
が、隠してもセキュリティが向上するわけではないらしい

file_uploads

ファイルアップロード機能を使わない場合はOff

log_errors

必ずOn

magic_quotes_gpc

自動的にバックスラッシュでエスケープ処理が行われる
addslashed()関数と同じ
PHP5.3で非推奨でPHP5.4で削除

max_execution_time

PHPスクリプトの最大実行時間を秒で指定
この値を長く設定するとDoS攻撃のリスクを高めるので注意

max_input_time

スクリプトを実行する前の入力データを解析する最大秒数
同様にDoS攻撃のリスクが高まる

memory_limit

PHPが使用可能なメモリの上限を指定する
DoS攻撃やバグによるメモリの課題消費を抑えるために32GBが適切っぽい

register_globals

Onにすると意図しない変数の初期化が行われる可能性がある
以下のコードは同じような危険がある
PHP5.3で非推奨に、PHP5.4で削除された

example.php
extract($_POST);
foreach ($_POST as $key => $val) {
    $key = $val;
}

safe_mode

共有サーバーではOn
PHP5.3で非推奨、PHP5.4で削除された

session.cookie_httponly

セッションCookieにHttpOnly属性を指定

session.cookie_lifetime

セッションCookieの有効期限を指定
0の場合はブラウザを終了するまで

session.cookie_path

セッションCookieを送信するパスの指定
共有サーバーの場合自分のディレクトリを指定しないと、
他のユーザーのディレクトリにアクセスした場合にセッションIDが漏洩したり、セッションが共有される可能性がある

session.cookie_secure

全ページSSLで保護されている場合はOn

session.entropy_file

セッションIDを作成する時のエントロピーソースを指定
存在する場合は/dev/urandom

session.gc_maxlifetime

セッションデータの生存時間を秒で指定
初期値は24分になっている
ただすぐに削除されるとは限らない
session.gc_probabilityとsession.gc_divisorの設定による確率で削除される
長くするとセッションハイジャックのリスクが高まる

session.hash_function

セッションIDを生成するハッシュ関数を指定する
MD5は128ビット、SHA1は160ビットなのでビット数の多い分だけセッションIDの推定が難しくなるので安全性が上がる

session.save_path

セッションファイルを保存するフォルダ
デフォルトでtmpになるが共有サーバーの場合は他のユーザーがセッションハイジャックする可能性がある

session.use_cookies

Cookieによるセッション管理を行うかどうか
必ずOnにする

session.use_only_cookies

URLによるセッション管理を禁止するかどうか
必ずOnにする

session.use_trains_sid

透過的なセッションIDをするかどうか
セキュリティ上の問題があるので0

まとめ

安全性を高めれば高めるほど制限が増えるので、ケースバイケースで変更するのがいいと思います!!

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

PHP の GD ライブラリを使って、「たくさんある画像の中で微妙に色違うやつ当てるゲーム」を少しでもチートされないように作りたい

ソースコードはこちら

「たくさんある画像の中で微妙に色違うやつ当てるゲーム(名前長い)」がよくあると思います。
これをフロントの世界 (HTML/CSS, JavaScript) だけで実装すると、目で答えがわからなくてもチートされて正解できてしまう懸念があります。
それを少しでもチートしにくくしようというのが今回の企画です。

top.gif

今回行う対策

開発者ツールから色の情報がわからないようにする

CSS で背景色を指定すると、開発者ツールから見えてしまいます。
そのため、バックエンドで画像を動的に生成したのち、フロントではそれをただ読み込むだけにします。

その他、「正誤判定をフロントエンドで実装しない」などの対策も必要ですが、記事では省略します。

参考ページ

PHPで画像を動的に生成する【GD編】 | バシャログ。

PHP を使います

今回は PHP の GD 拡張モジュールを用いて画像を生成します。
GD 拡張モジュールはよほど古い PHP でなければ同梱されているはずです。
php.ini を開き、以下のような行があったら先頭のセミコロンを消去して有効にします。

;extension=gd2

お手持ちの PHP で phpinfo() を実行し、 GD に関する情報が見えていることを確認してください。
次のように見えていれば成功です。

gd.png

実際に作ってみる

GD ライブラリを使って 100×100 の単色画像(色はランダム)を作成し、出力する

$width = 100;
$height = 100;

// 画像リソースを作成する
$img = imagecreate($width, $height);

// 色を付ける
imagecolorallocate($img, rand(0, 255), rand(0, 255), rand(0, 255));

// 出力する
$filename = 'images/image.png';
imagepng($img, $filename);

imagecreate 関数で新規画像を作成し、 imagecolorallocate 関数で着色、 imagepng 関数でファイルに出力します。

3 枚の単色画像(1 枚だけ色が異なる)を出力し、正解データを保持する

画像を 1 枚出力するパターンを上でやったので、次は複数枚(今回は 3 枚)出力して、正解を示すものも出力します。
着色パターンは正解・不正解の 2 通り用意すれば OK で、正解データはテキストファイルに数字だけ書いておきます。

メインのロジックです。

create_images.php
<?php

require_once __DIR__ . '/Image/ImageResourceFactory.php';
require_once __DIR__ . '/Image/Image.php';

use Image\Image;

function main($imageCount)
{
    $correctColor = array(
        'red' => getRandomInt(0, 255),
        'green' => getRandomInt(0, 255),
        'blue' => getRandomInt(0, 255),
    );
    $incorrectColor = array(
        'red' => getRandomInt(0, 255),
        'green' => getRandomInt(0, 255),
        'blue' => getRandomInt(0, 255),
    );

    $collectImageIndex = getRandomInt(0, $imageCount - 1);

    $width = 100;
    $height = 100;

    for ($i = 0; $i < $imageCount; ++$i) {
        $filename = __DIR__ . '/../data/image' . $i . '.png';
        if ($i === $collectImageIndex) {
            outputImage($width, $height, $correctColor, $filename);
        } else {
            outputImage($width, $height, $incorrectColor, $filename);
        }
    }

    file_put_contents(__DIR__ . '/../data/answer.txt', $collectImageIndex);
}

function getRandomInt(int $min, int $max = null, int $fixed = null): int
{
    if (isset($fixed)) {
        return $fixed;
    }
    return rand($min, $max);
}

function outputImage(int $width, int $height, array $color, string $filename)
{
    try {
        $image = new Image($width, $height);
        $image->paintIn($color['red'], $color['green'], $color['blue']);
        $image->saveAs($filename);
    } catch (Exception $e) {
    }
}

メインのロジックを実行するにあたりクラスとして切り出した、画像の処理類です。

Image/Image.php
<?php

namespace Image;

use Exception;
use Image\ImageResourceFactory;

/**
 * Class Image
 * 画像の作成・編集・保存
 */
class Image
{
    /**
     * @var resource $resource - 画像リソース
     */
    public $resource;

    /**
     * Image constructor.
     * @param int $width - 画像の幅
     * @param int $height - 画像の高さ
     * @throws Exception - 画像作成に失敗した場合、例外を返します。
     */
    public function __construct(int $width, int $height)
    {
        $this->resource = ImageResourceFactory::getImageResource($width, $height);
    }

    public function paintIn(int $red, int $green, int $blue)
    {
        imagecolorallocate($this->resource, $red, $green, $blue);
    }

    public function saveAs(string $filename)
    {
        imagepng($this->resource, $filename);
    }
}
Image/ImageResourceFactory.php
<?php

namespace Image;

use Exception;

/**
 * Class ImageResourceFactory
 * 画像リソース作成用
 */
class ImageResourceFactory
{
    /**
     * 画像のリソースを取得します。
     *
     * @param int $width - 画像の幅
     * @param int $height - 画像の高さ
     * @return resource - 画像リソース
     * @throws Exception - 画像作成に失敗した場合、例外を返します。
     */
    public static function getImageResource(int $width, int $height)
    {
        $resource = imagecreate($width, $height);
        if ($resource === false) {
            throw new Exception('Failed to create image resource.');
        }
        return $resource;
    }
}

正誤判定についても、フロントの JavaScript ではなく PHP で実装し、画面にリンクを貼ります(ここでは省略します)。

感想

この類のゲームは前から作ろうと思っていたのですが、広告系の業務をしていたことから base64 形式を使った方法を探っていました。
base64 では 1 x 1 の黒色・透明画像の作り方ぐらいしか見つからなくて、いろいろ途方に暮れていたのですが、今回 PHP の中に画像を作れるライブラリが同梱されていたことに気づけたのは大きいです。

正直なところ、オンライン対戦やインターネットランキングなどを含まないゲームなら、チートするかどうかはエンドユーザの自由であって、わざわざバックエンドに処理をやらせる必要もないでしょう。
ですが、「フロントはすべて見えていて、いくらでも改造できる」という観点から、チートされたくない部分をバックエンドにより実装できたことは、今後の業務にも活かせそうな部分ではあると思います。

今回の記事がみなさんの何かのきっかけにでもなれば幸いです。

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

PHPでtwitter投稿する

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

PHP Laravel 6 おすすめ映画投稿サイト作成過程 3:詳細ページ作成編

showページの追加

recommendsディレクトリにshow.blade.phpを作成します。
Screen Shot 2020-09-23 at 15.22.49.png

コントローラー追加

ルーティングは以下の通りです。

|        | GET|HEAD  | recommends/{recommend}      | recommends.show    | App\Http\Controllers\RecommendController@show                          | web 

showメソッドで{recommend}を受け取り、show.blade.phpへ投げます。
なお、recommends/{recommend}の{recommend}にはDBのid番号が入ります。

recommend/app/Http/Controllers/RecommendController.php
 public function show(Recommend $recommend)
    {
        return view('recommends.show', compact('recommend'));
    }

showで受け取ったデータの出力方法

{{$recommend-> カラム名}}で各データを表示できます。

recommend/resources/views/recommends/show.blade.php
<table>
    <tr>
       <th>{{$recommend->title}}</th>
       <th>{{$recommend->image_file_name}}</th>
       <th>{{$recommend->url}}</th>
       <th>{{$recommend->description}}</th>
       <th>{{$recommend->Impressions}}</th>
      </tr>
</table>

リンクの作成

showページへのリンクは、以下の方法で設定できます。
なお、今回はindexページのタイトルにリンク設定しました。

recommend/resources/views/recommends/index.blade.php
<a href="{{route('recommends.show', $recommend->id)}}">{{$recommend->title}}</a>

ちなみに、以下でも同じ動作を実現できます。
この表記では、該当するデータを{recommend}としてコントローラー側で受け取っていることが分かり易い反面、ルーティングを変更した場合に機能しなくなってしまうので注意が必要です。

recommend/resources/views/recommends/index.blade.php
<a href="recommends/{$recommend->id}">{{$recommend->title}}</a>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】GuzzleでEUC-JPのページを受信すると文字化け→cURLの受信結果を適切にエンコードして解決

現象

  1. LaravelでHTTP通信するならGuzzleらしい
  2. 試す。受信結果が文字化けする
  3. cURLベースで書き直す
  4. やっぱり文字化けする
  5. EUC-JPのページで発生している

解決

以下のエントリを参考に、cURLでの受信結果を適切に変換することで文字化けを防ぐことができた。

コード

function curl_get_contents($url, $timeout = 60) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
    $result = $this->curl_exec_utf8($ch);
    curl_close($ch);
    return $result;
  }

  function curl_exec_utf8($ch) {
    $data = curl_exec($ch);
    if (!is_string($data))
        return $data;

    unset($charset);
    $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

    /* 1: HTTP Content-Type: header */
    preg_match('@([\w/+]+)(;\s*charset=(\S+))?@i', $content_type, $matches);
    if (isset($matches[3]))
        $charset = $matches[3];

     /* 2: <meta> element in the page */
     if (!isset($charset)) {
      preg_match( '@<meta\s+http-equiv="Content-Type"\s+content="([\w/]+)(;\s*charset=([^\s"]+))?@i', $data, $matches );
      if ( isset( $matches[3] ) ) {
          $charset = $matches[3];
          /* In case we want do do further processing downstream: */
          $data = preg_replace('@(<meta\s+http-equiv="Content-Type"\s+content="[\w/]+\s*;\s*charset=)([^\s"]+)@i', '$1utf-8', $data, 1);
      }
    }

    /* 3: <xml> element in the page */
    if (!isset($charset)) {
        preg_match( '@<\?xml.+encoding="([^\s"]+)@si', $data, $matches );
        if ( isset( $matches[1] ) ) {
            $charset = $matches[1];
            /* In case we want do do further processing downstream: */
            $data = preg_replace('@(<\?xml.+encoding=")([^\s"]+)@si', '$1utf-8', $data, 1);
        }
    }

    /* 4: PHP's heuristic detection */
    if (!isset($charset)) {
        $encoding = mb_detect_encoding($data);
        if ($encoding)
            $charset = $encoding;
    }

    /* 5: Default for HTML */
    if (!isset($charset)) {
        if (strstr($content_type, "text/html") === 0)
            $charset = "ISO 8859-1";
    }

    /* Convert it if it is anything but UTF-8 */
    /* You can change "UTF-8"  to "UTF-8//IGNORE" to
      ignore conversion errors and still output something reasonable */
    if (isset($charset) && strtoupper($charset) != "UTF-8")
        $data = iconv($charset, 'UTF-8', $data);

    return $data;
  }

gzipオプションについて

curl_setopt($ch, CURLOPT_ENCODING, 'gzip');はサーバー依存です。

筆者の環境では、gzipで圧縮されたレスポンスが返ってくるため必要でした。
(参考:PHPのcurl()でレスポンスが文字化けしたら確認すること2つ - PHP | ゆるりの足あと

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

nknino

knknkn

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

PHP Laravel 6 おすすめ映画投稿サイト作成過程 2:一覧ページ作成編

viewファイルの追加

一覧を表示するため、今回は新たに作成したrecommensディレクトリにindex.blade.phpを作成しました。
Screen Shot 2020-09-23 at 13.18.06.png

コントローラーの設定

コントローラー側では$recommendsをviewに渡します。

recommend/app/Http/Controllers/RecommendController.php
  public function index()
    {
      $recommends = Recommend::all();
        return view('recommends.index', ['recommend' => $recommends]);
    }

ダミーデータの入力

5件のダミーデータを入力します。
今回は、未入力が許されないタイトルとURLのみ指定します。

useにコントローラーモデルを追加

recommend/database/seeds/RecommendSeeder.php
 use Illuminate\Database\Seeder;//元から記載されている。
 use App\Models\Recommend; //追加。

for文で5件のダミーデータを作成

recommend/database/seeds/RecommendSeeder.php
 public function run()
    {
      for($i = 1; $i < 6; $i++){
        Recommend::create([
          'title' => 'test{$i}',
          'url' => 'url{$i}'
        ]);
      };
    }

上記をデータベースシーダーに流し込む

recommend/database/seeds/DatabaseSeeder.php
 public function run()
    public function run()
    {
        $this->call(RecommendSeeder::class);
    }

シーダーを実行

 $php artisan db:seed

以上でダミーデータ作成完了です。

index.blade.phpでDB一覧の表示

今回は、タイトルとタイトル画像を一覧で表示することにします。
手段はforEachでの展開です。

recommend/resources/views/recommends/index.blade.php
@foreach($recommends as $recommend)
   <tr>
      <th>{{$recommend->title}}</th>
      <th>{{$recommend->url}}</th>
   </tr>
@endforeach

ページネーション

表示数が多くなった場合には、以下の方法でページを分割することができます。
(加筆予定)

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

core dump と gdb コマンドがあればバイナリ再ビルドしなくてもデバッグできます(PHPコマンドでSegmentation faultになった原因調査)

発生した問題

ある Laravel Artisan のコマンドを実行しようとしたら、即座に他に出力もなく Segmentation fault が出て終了しました。
1回目は実行できるのだけど、2回目は実行できない(結論からいうとキャッシュ周りの問題なので、キャッシュがあると死んでました)。

$ php artisan command:hogehoge
Segmentation fault

バージョン情報

$ php -v
PHP 7.2.22 (cli) (built: Sep 11 2019 01:44:09) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.22, Copyright (c) 1999-2018, by Zend Technologies
$ gdb -v
GNU gdb (GDB) Amazon Linux (7.6.1-64.33.amzn1)
$ uname -r
4.14.146-93.123.amzn1.x86_64

問題解決に使えるツール

どうやって問題を追っていくか?

  1. core dump ファイルを吐き出させる
  2. Segmentation fault を吐いたコマンドのパスを確認する
  3. gdb に両方セットで食わせます gdb <実行コマンドのパス> -c <core dump file>
    • 一般的にはPHPコマンド自体を再ビルドしてgdbコマンド経由で実行することでデバッグする方法をよく見ますが、手間がかかりハードルが高いです
    • バイナリを再ビルドしなくてもcore dumpさえあれば簡易的なデバッグができるというところで表題の通りです
  4. gdb コンソールで where コマンドを実行するとスタックトレースが表示されます
  5. スタックトレースをとっかかりにググるなり調査します

詳細

1. core dump ファイルを吐き出させる

Segmentation fault が起こった時には core dump を吐き出すことができます

現状の設定を確認。0 になっているので吐き出されません。

$ ulimit -a | grep core
core file size          (blocks, -c) 0 

設定を変更します。これを入れると性能劣化すると思うので、出来るだけ本番環境は避けた方が無難です。私の環境では開発環境で問題を再現していたので開発環境にいれました。
※以下のコマンドで反映するのは、sshで接続している1セッションだけです。このセッションでコマンド実行する場合には永続化は不要ですが、他のバックグラウンドプロセスに実行させたり、ApacheによるPHP実行のcore dumpを吐き出させる時には他の記事を参照してください。

$ ulimit -c unlimited
$ ulimit -a | grep core
core file size          (blocks, -c) unlimited

以下のように改めて問題が起こるコマンドを実行すると(core dumped)が追加されているのが確認できます。

# php artisan command:hogehoge
Segmentation fault (core dumped)

core dump file の出力先は以下のファイルに書かれています。参考: https://www.suse.com/ja-jp/support/jp/kb/tids/00100037/
下記の出力は、コマンド実行したカレントディレクトリを示しています。

$ cat /proc/sys/kernel/core_pattern
core

以下のようなファイル出力がされているはずです。

$ ls core.6888
core.6888

2. Segmentation fault を吐いたコマンドのパスを確認する

$ which php
/usr/bin/php

3. gdb に両方セットで食わせます

gdb <実行コマンドのパス> -c <core dump file>

最終的に_emalloc ()で落ちているのはわかりましたが、情報がまだ足りません。

[root@ip-172-31-6-236 html]# gdb /usr/bin/php -c core.6888
GNU gdb (GDB) Amazon Linux (7.6.1-64.33.amzn1)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-amazon-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/bin/php-7.2...(no debugging symbols found)...done.
warning: core file may not match specified executable file.
[New LWP 6888]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `php artisan command:hogehoge'.
Program terminated with signal 11, Segmentation fault.
#0  0x00000000005a0c11 in _emalloc ()
Missing separate debuginfos, use: debuginfo-install php72-cli-7.2.13-1.7.amzn1.x86_64
(gdb)

4. gdb コンソールで where コマンドを実行するとスタックトレースが表示されます

上記の状態でgdbの対話状態になっていると思うので、以下のように whereコマンドを発行します。
今回のケースだと、memcachedのクライアントに問題があり、igbinary という module を使って unserialize する時に問題が発生することがわかりました。

(gdb) where
#0  0x00000000005a0c11 in _emalloc ()
#1  0x00007fdbe5286735 in ?? () from /usr/lib64/php/7.2/modules/igbinary.so
#2  0x00007fdbe528ce46 in igbinary_unserialize () from /usr/lib64/php/7.2/modules/igbinary.so
#3  0x00007fdbe260f829 in ?? () from /usr/lib64/php/7.2/modules/memcached.so
#4  0x00007fdbe261247c in ?? () from /usr/lib64/php/7.2/modules/memcached.so
#5  0x00007fdbe2613a68 in ?? () from /usr/lib64/php/7.2/modules/memcached.so
#6  0x000000000066bd55 in execute_ex ()
#7  0x00000000005b719b in zend_call_function ()
#8  0x00000000004fd263 in ?? ()
#9  0x000000000066b77a in execute_ex ()
#10 0x000000000066c5f3 in zend_execute ()
#11 0x00000000005c6d54 in zend_execute_scripts ()
#12 0x0000000000565af0 in php_execute_script ()
#13 0x000000000066e9b6 in ?? ()
#14 0x000000000042d18e in ?? ()
#15 0x00007fdbf9e14445 in __libc_start_main () from /lib64/libc.so.6
#16 0x000000000042d213 in _start ()

5. スタックトレースをとっかかりにググるなり調査します

今回のケースだと この記事 を参考対応を取りました。igbinary シリアライザを使う必然性がなかったので設定の変更を行って解決しました。この対応についての詳細については本記事のスコープから外れるので詳しくは記述しません。

before

$ php -i | grep memcached.serializer
memcached.serializer => igbinary => igbinary

after

$ php -i | grep memcached.serializer
memcached.serializer => php => php

参考

http://sarface2012.hatenablog.com/entry/20101027

2010年の記事ですが色褪せないです。ありがとうございました。

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

PHP Laravel 6 おすすめ映画投稿サイト作成過程 1:準備編

備忘録としての制作過程を記録

Laravel を実装(バージョン6)

composer create-project --prefer-dist laravel/laravel recommend "6.*"

一部内容の修正

.env

DBはsqliteを使用する。

recommend/.env
DB_CONNECTION=sqlite

app.php

ロケーション等の変更。

recommend/config/app.php
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',
'faker_locale' => 'ja_JP',

DB作成

touch database/database.sqlite

DB関連の資料作成

必要なファイルを一括生成

$ php artisan make:model Models/Recommend -a

テーブル内容を設定

今回は、以下の6項目

カラム
ID
映画タイトル
映画の画像
映画イメージの名前
映画URL
映画概要(空投稿可)
感想(空投稿可) 
recommend/database/migrations/2020_09_23_105017_create_recommends_table.php
public function up()
    {
        Schema::create('recommends', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->string('image_file_name', 100)->nullable();
            $table->string('image_title',100)->nullable();
            $table->string('url');
            $table->string('description')->nullable();
            $table->string('Impressions')->nullable();
            $table->timestamps();
        });
    }

マイグレーション

マイグレーションファイルがデータベースに反映されます。

$ php artisan migrate

autuの実装

以下の作業で、認証に必要なすべてのビューがresources/views/authディレクトリに生成されます。

ルート定義

composer require laravel/ui "^1.0" --dev
php artisan ui vue --auth
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像付きExcelをkintoneに移行してみる

Excelのデータをkintoneに登録したい!!
もちろん、Excelの文字データのみであれば、kintoneに一括登録することはブラウザ上からもできます。

が、そのExcelに画像が貼られたファイルの場合はどうでしょう。
文字データと、それに関連付けて画像ファイルも一括登録させたいですね。

例えば、このようなExcelファイル
ramen1.png

文字データと、それに関連付けて画像ファイルも一括登録させたい

これを実現してくれるツールがあるということで、separator-excelcli-kintone を使って試してみました。

流れ

  1. separator-excel で、CSVファイルと画像ファイルに分離する
  2. kintoneで、アプリを作成する
  3. cli-kintone で、kintoneにデータをインポートする

ツールやサービスはこんな流れで進めてみます。
それでは早速、やってみよう。

separator-excelで、CSVファイルと画像ファイルに分離する

PHPなどの環境を構築する

separator-excel のREADME を参考に、「必須環境」をインストールしていきます。

// MacOSの場合
brew install git
// パッケージを探す
brew search php74 
brew install php@7.4
brew install composer

インストールされていることも兼ねて、Version確認しましょう。

git --version
php -v
composer -V

PHPインストール後にVersionが変わらない場合には、PATHを通そうを参考に。

$ php -v
PHP 7.4.10 (cli) (built: Sep  3 2020 18:20:30) ( NTS )

よし、PHPのVersion7.4なので、動くはず。

separator-excelをインストールする

インストール手順に沿って、インストールしていきます。

実行してCSVファイルと画像ファイルを作る

インポートする ramen.xlsx Excelファイルを以下のディレクトリにダウンロードしておきましょう。

  • ramen.xlsx: separator-excel/tests/_data

separator-excelに指定する出力パラメータは以下のようにしました。

  • csv: tests/_output/export.csv
  • files directory: tests/_output
$ php ./bin/separator-excel tests/_data/ramen.xlsx -o tests/_output/export.csv -b tests/_output/

実行結果を確認する

export.csv と 画像ファイル一式が無事に出力されました〜〜。
ramen2.png

export.csv の中身はこんな感じ。
ramen3.png

kintoneアプリを作る

kintoneの環境を用意し、出力したCSVファイルからkintoneアプリを作成してみます。
kintoneの環境がない方は「kintone開発者ライセンス」を申し込んでください。

CSVファイルを読み込んでkintoneアプリを作成する

読み込む文字コード、読み込むフィールドタイプなどをこのように設定しましょう。
ramen4.png

添付ファイルフィールドを追加する

ramen5.png

添付ファイルフィールドの設定を変更する

特にフィールドコードは重要です。これをCSVのフィールド名と合わせておかないと、インポートできなくなります。
ramen6.png

他のフィールドコードも設定する

フィールド名 フィールドコード
場所 場所
感想 感想
写真 写真

APIトークンを作成する

kintoneアプリの設定画面で、APIトークンを生成し、レコード追加をチェックします。(保存とアプリ更新も忘れずに)
ドメイン、アプリID、APIトークンはcli-kintoneで利用しますので、テキストエディタなどに記録しておきましょう。

ramen8.png

データを削除する

いまは、画像ファイルが関連付けられていないデータが登録されているため、レコード一覧から削除する か、レコードを一括で削除する かの方法で、データを削除しましょう。
ramen9.png

cli-kintone を使ってインポートする

OS環境にあった cli-kintone ファイルをダウンロードします。

separator-excel > tests に、cli-kintoneを配置しました。
次のように実行していきます。※MacOSの場合

$ cd separator-excel/tests
// <APP_ID>, <FQDN>, <API_TOKEN> は各環境で異なるので、値を設定してください。
$ ./cli-kintone --import -a <APP_ID> -d <FQDN> -e utf-8 -t <API_TOKEN> -f _output/export.csv -b _output
// 僕の環境ではAPIトークンはマスクしていますが、次のように実行しました
//$ ./cli-kintone --import -a 27 -d sample.cybozu.com -e utf-8 -t G7e~~~~~ -f _output/export.csv -b _output

実行すると、ログではインポートされている様子。

 [2020-09-21 10:13:47]: Start from lines: 1 - 6 => SUCCESS
 [2020-09-21 10:13:48]: DONE

kintoneアプリでも無事にデータとファイルがインポートされていました〜〜!!
ramen10.png

さいごに

今回は、Excelの中にあるデータと画像ファイルを、きちんと関連付けてkintoneにインポートしてみました。Excelで管理しているものはとても多いと思います。
社員名簿、不動産や賃貸リスト、建設現場、備品管理などなど。今あるExcelをクラウド共有サービスのkintoneに移行して、いつでも、どこでも、最新情報にアクセスできる環境づくりはいかがでしょうか。

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

PHP Laravelのmake:modelオプション

make:modelに用意された様々なオプション

モデルの作成時には、コントローラやマイグレーションファイル等の作成が必要となりますが、いちいち作成するのは面倒です。
make:modelには以下のオプションが用意されているので、状況によって使い分けましょう。

$ php artisan make:model -h

オプションの内容は、コンソールに php artisan make:model -h を入力することで確認できます。
内容は以下の通りです。基本的には -a で全て作成することになると思いますが、必要に応じてそれ以外を使い分けましょう。

Options:
  -a, --all             Generate a migration, seeder, factory, and resource controller for the model
  -c, --controller      Create a new controller for the model
  -f, --factory         Create a new factory for the model
      --force           Create the class even if the model already exists
  -m, --migration       Create a new migration file for the model
  -s, --seed            Create a new seeder file for the model
  -p, --pivot           Indicates if the generated model should be a custom intermediate table model
  -r, --resource        Indicates if the generated controller should be a resource controller
      --api             Indicates if the generated controller should be an API controller
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

-a で作成されたコントローラー

以下のように遷移先も自動で作成してくれます。
できる限りオプションを使用して作成しましょう。

****Controller.php
class RecommendController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Models\Recommend  $recommend
     * @return \Illuminate\Http\Response
     */
    public function show(Recommend $recommend)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Models\Recommend  $recommend
     * @return \Illuminate\Http\Response
     */
    public function edit(Recommend $recommend)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Recommend  $recommend
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Recommend $recommend)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Models\Recommend  $recommend
     * @return \Illuminate\Http\Response
     */
    public function destroy(Recommend $recommend)
    {
        //
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel バリデーションを設定する ~フォームリクエスト編~

目的

  • リクエストにバリデーションを記載する方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.3)
ハードウェア MacBook Pro (16-inch ,2019)
プロセッサ 2.6 GHz 6コアIntel Core i7
メモリ 16 GB 2667 MHz DDR4
グラフィックス AMD Radeon Pro 5300M 4 GB Intel UHD Graphics 630 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いて導入
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いて導入

前提情報

  • ターミナルコマンドは基本的に$ laravel newコマンドで作成されたアプリ名ディレクトリで実行するものとする。
  • ブラウザ上でコンテンツを新規作成する際にコンテンツタイトルとコンテンツ詳細に入力がない場合にバリデーションで弾く処理を追加する。

概要

  1. フォームリクエストクラスの作成
  2. コントローラの修正
  3. ビューファイルの修正
  4. 確認

詳細

  1. フォームリクエストクラスの作成

    1. 下記コマンドを実行してフォームリクエストクラファイルの作成を行う。

      $ php artisan make:request StoreContentPost
      
    2. 下記コマンドを実行して先に作成したフォームリクエストクラスファイルを開く。

      $ vi app/Http/Requests/StoreContentPost.php
      
    3. 開いたフォームリクエストクラスファイルを下記の様に修正する。

      アプリ名ディレクトリ/app/Http/Requests/StoreContentPost.php
      <?php
      
      namespace App\Http\Requests;
      
      use Illuminate\Foundation\Http\FormRequest;
      
      class StoreContentPost extends FormRequest
      {
          /**
           * Determine if the user is authorized to make this request.
           *
           * @return bool
           */
          public function authorize()
          {
              return true;
          }
      
          /**
           * Get the validation rules that apply to the request.
           *
           * @return array
           */
          public function rules()
          {
              return [
                  //バリデーションルールを記載する
                  //'チェックするデータ名' => 'ルール1|ルール2'の様にルールを定義する
                  'title' => 'required',
                  'detail' => 'required',
              ];
          }
      }
      
  2. コントローラファイルの修正

    1. 下記コマンドを実行してコントローラファイルを作成する。

      $ vi app/Http/Controllers/ContentController.php
      
    2. 開いたコントローラファイルを下記の様に修正する。

      アプリ名ディレクトリ/app/Http/Controllers/ContentController.php
      <?php
      
      namespace App\Http\Controllers;
      
      //use Illuminate\Http\Request;
      //下記を追記する
      use App\Http\Requests\StoreContentPost;
      
      use App\Content;
      
      class ContentController extends Controller
      {
          public function index()
          {
              $contents = Content::all();
              return view('contents.index', ['contents' => $contents]);
          }
      
          public function create()
          {
              return view('contents.create');
          }
      
          //下記を修正する
          public function save(StoreContentPost $request)
          {
              $new_content = new Content();
              $new_content->title = $request['title'];
              $new_content->detail = $request['detail'];
              $new_content->save();
      
              return redirect(route('content.index'));
          }
      
          public function detail($content_id)
          {
              $content = Content::find($content_id);
              return view('contents.detail', ['content' => $content]);
          }
      
          public function edit($content_id)
          {
      
              $content = Content::find($content_id);
              return view('contents.edit', ['content' => $content]);
          }
      
          //下記を修正する
          public function update($content_id, StoreContentPost $request)
          {
              $content = Content::find($content_id);
              $content->title = $request['title'];
              $content->detail = $request['detail'];
              $content->save();
              return redirect(route('content.index'));
          }
      
          public function destroy($content_id)
          {
              $content = Content::find($content_id);
              $content->delete();
              return redirect(route('content.index'));
          }
      
      }
      
  3. ビューファイルの修正

    1. 下記コマンドを実行して新規登録する時に表示しているビューファイルを開く。

      $ vi resources/views/contents/create.blade.php
      
    2. 開いたビューファイルを下記の様に修正してエラー文を表示できる様にする。

      アプリ名ディレクトリ/resources/views/contents/create.blade.php
      <h1>create</h1>
      
      <form action="{{route('content.save')}}" method="post">
          @csrf
          <h3>タイトル</h3>
          {{-- 下記を追記する --}}
          @if ($errors->has('title'))
              @foreach ($errors->get('title') as $title_errors)
                  <p>{{$title_errors}}</p>
                  <br>
              @endforeach
          @endif
          {{-- 上記までを追記する --}}
          <input type="text" name="title">
          <br>
          <h3>内容</h3>
          {{-- 下記を追記する --}}
          @if ($errors->has('detail'))
              @foreach ($errors->get('detail') as $detail_errors)
                  <p>{{$detail_errors}}</p>
                  <br>
              @endforeach
          @endif
          {{-- 上記までを追記する --}}
          <textarea name="detail" cols="30" rows="10"></textarea>
          <br>
          <input type="submit" value="保存">
          <input type="button" onclick="location.href='{{ route('content.index') }}'" value="一覧に戻る">
      </form>
      
  4. 確認

    1. 各種情報入力画面でバリデーションルールと異なる情報を入力した時にエラーが表示されれば作業完了である。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

front-end に Nuxt.js と Laravel が混在するシステムで Cookie を用いてクライアントを識別する

Nuxt-2.14.4 Laravel-6.x

前回の記事の続きですが、 A/B テストのような文脈でクライアント(ブラウザ)ごとに一意なランダムの ID を割り当て、 Cookie を用いてリクエストごとにクライアントを分類できるようにする処理を実装する機会がありました。この記事では front-end に Nuxt.js と Laravel (の view )が混在するシステムで、両方から ID を生成・利用できるようにする方法について紹介します。

方針と注意

基本的な方針は以下の通りです。

  1. リクエストの Cookie ヘッダから ID が得られなかった場合(初回訪問)は ID を生成して Cookie に追加 1 する。
  2. リクエストの Cookie ヘッダから ID を得られた場合(2回目以降の訪問)は、それを利用する。

今回の実装では Nuxt.js と Laravel のどちら側でも Cookie から ID を読み取れるようにするため、 HttpOnly 属性を外し、暗号化を行いません。このため、ログインセッション用の Cookie など、第三者による Cookie の盗聴や改ざんがセキュリティ上のリスクとなるような情報に関しては本記事中の実装方法を避け、フレームワークが提供する機能などを利用してください。

また front-end がすべて Nuxt.js で構成されている場合には Laravel 側の処理は不要なので、 Laravel のセクションを読み飛ばしてください。

実装

Nuxt.js

以下の useClientId は ID の生成および Cookie 操作の処理を集約した composition です。

app/compositions/client-id.ts
import { Store } from 'vuex/types'
import { IncomingMessage } from 'connect'
import Cookie from 'universal-cookie'
import {
  name,
  StoreAbTesting,
  ACTIONS,
  GETTERS
} from '~/store/abTesting'

export function useClientId(store: Store<StoreAbTesting>, req: IncomingMessage) {
  const CLIENT_ID_KEY = 'client_id'

  const cookie = process.server
    ? new Cookie(req.headers?.cookie ?? '')
    : new Cookie()

  const getIdFromStore = (): string => {
    return store.getters[`${name}/${GETTERS.clientId}`]
  }

  const prepareId = async (): Promise<void> => {
    if (process.client) {
      throw new Error('CSR でのメソッド呼び出しは不正です')
    }

    const generateId = (): string => {
      // ランダムな ID を生成して返す
    }

    await store.dispatch(
      `${name}/${ACTIONS.setClientId}`,
      getIdFromCookie() ?? generateId()
    )
  }

  const getIdFromCookie = (): string|undefined => {
    return cookie.get(CLIENT_ID_KEY)
  }

  const isIncludedInCookie = (): boolean => {
    return getIdFromCookie() !== undefined
  }

  const setCookie = (): void => {
    if (process.server) {
      throw new Error('SSR でのメソッド呼び出しは不正です')
    }

    if (isIncludedInCookie()) {
      return
    }

    cookie.set(CLIENT_ID_KEY, getIdFromStore())
  }

  return {
    prepareId,
    setCookie,
    getId: getIdFromStore
  }
}

主要な処理について解説します。

まず Cookie の操作に関しては universal-cookie というライブラリを利用しています。

  const cookie = process.server
    ? new Cookie(req.headers?.cookie ?? '')
    : new Cookie()

このライブラリを利用すると Nuxt の SSR モードでもリクエストヘッダから Cookie を取得することができます。使用例に関しては以下の記事が参考になります。

Nuxt は Context から context.req のようにしてリクエストの情報が取得できるので、 useClientId の利用時はこの変数を引数に入力します。この変数から req.headers?.cookie のようにして Cookie ヘッダの値を文字列として取得できるので、これを Cookie コンストラクタの引数に渡してインスタンス生成しています。

次に Cookie から ID を取得、または生成を行う prepareId メソッドです。

  const prepareId = async (): Promise<void> => {
    if (process.client) {
      throw new Error('CSR でのメソッド呼び出しは不正です')
    }

    const generateId = (): string => {
      // ランダムな ID を生成して返す
    }

    await store.dispatch(
      `${name}/${ACTIONS.setClientId}`,
      getIdFromCookie() ?? generateId()
    )
  }

最初の処理で CSR でのメソッド呼び出しを禁止しています。これは直アクセス時の SSR の時点で ID を用意しておけば以降の処理で ID を使い回せるので、 CSR での呼び出しが不要になるためです。

getIdFromCookie() ?? generateId() の部分で Cookie から ID を取得するか、取得できなければ生成を行っています。取得した ID は Vuex の Store を用いて保存しておきます 2。 Store に関しても Nuxt の Context から context.store のように取得できます。何となく cookie.set(id) のようにしておけば後から cookie.get() で ID を取得できそうな気もしてしまいますが、 SSR で cookie.set() を呼び出してもブラウザ上の Cookie には影響がないため、このような方法では実現できません。

次に Cookie を設定する setCookie メソッドです。

  const setCookie = (): void => {
    if (process.server) {
      throw new Error('SSR でのメソッド呼び出しは不正です')
    }

    if (isIncludedInCookie()) {
      return
    }

    cookie.set(CLIENT_ID_KEY, getIdFromStore())
  }

前述の通り SSR で cookie.set() を呼び出しても意味がないので、 SSR でのメソッド呼び出しを禁止しています。そして ID が Cookie に含まれていない場合のみ Store に保存しておいた ID を取得して Cookie に設定します。

このように実装した useClientId の諸々の処理は plugins から呼び出します。

app/plugins/ab-testing.ts
import { Context } from '@nuxt/types'
import { useClientId } from '~/compositions/libs/ab-testing/client-id'

export default (context: Context) => {
  if (process.server) {
    useClientId(context.store, context.req).prepareId()
  } else {
    useClientId(context.store, context.req).setCookie()
  }
}

前述した通り、 prepareId は SSR で、 setCookie は CSR で呼び出します。これらの処理を Middleware から呼び出しても問題ないように思えてしまいますが、 Middleware の場合は直アクセス時に SSR の処理しか実行されないため Cookie が設定されません。 Nuxt のライフサイクルに関しては以下の記事が参考になります。

後は nuxt.config.js に plugins を追加し、 Vue コンポーネント等から useClientId(store, req).getId() のような形で ID を取得する流れになります。

Laravel

大まかな内容は Nuxt.js と変わりませんが、 Laravel の場合は Store の代わりに単なる singleton インスタンスとしてメモリに保持する方針で実装します 3

まず singleton インスタンスの元となるクラス AbTestingCookie を以下のように作成します。

app/Http/Cookie/AbTestingCookie.php
<?php

namespace App\Http\Cookie;

use Illuminate\Http\Request;

class AbTestingCookie
{
    private $id;

    public function __construct(Request $request)
    {
        $key = 'client_id';

        if ($request->hasCookie($key)) {
            $this->id = $request->cookie($key);
            return;
        }

        $this->id = $this->make();
    }

    public function make()
    {
        // ランダムな ID を生成して返す
    }

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

コンストラクタでは Request を引数に取り、 Cookie から ID を取得するか、取得できなければ生成し、 ID をプロパティに保持しておきます。

singleton を生成するための ServiceProvider の実装は以下のとおりです。

app/Providers/AbTestingServiceProvider.php
<?php

namespace App\Providers;

use App\Http\Cookie\AbTestingCookie;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;

class AbTestingServiceProvider extends ServiceProvider
{
    public function boot(Request $request)
    {
        $this->app->singleton(AbTestingCookie::class, function ($app) use ($request) {
            return new AbTestingCookie($request);
        });
    }
}

次にレスポンスの Set-Cookie ヘッダに ID を付与する処理を Middleware で実装します。

app/Http/Middleware/IssueAbTestingClientId.php
<?php

namespace App\Http\Middleware;

use App\Http\Cookie\AbTestingCookie;
use Closure;

class IssueAbTestingClientId
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $key = 'client_id';

        if ($request->hasCookie($key)) {
            return $next($request);
        }

        return $next($request)->withCookie(cookie(
            $key,
            app(AbTestingCookie::class)->id(),
            0,
            null,
            null,
            false,
            false,  // front-end からも利用するため httpOnly にしない
            false,
            null
        ));
    }
}

この Middleware は ID の生成・利用が必要なすべてのルートで呼び出されるように Kernel.php に設定しておきます。また withCookie() メソッドはデフォルトで HttpOnly 属性を付与しますが、 Nuxt.js からも読み出せるように HttpOnly 属性を外しています。

最後に Nuxt.js から Cookie を読み出せるように EncryptCookies の暗号化の処理対象から除外しておきます。

app/Http/Middleware/EncryptCookies.php
<?php

namespace App\Http\Middleware;

use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;

class EncryptCookies extends Middleware
{
    /**
     * The names of the cookies that should not be encrypted.
     *
     * @var array
     */
    protected $except = [
        'client_id', // front-end からも利用するため暗号化しない
    ];
}

後は view 等から app()->make(AbTestingCookie::class)->id() のようにして ID を取得する流れになります。


  1. サーバー側ではレスポンスに Set-Cookie ヘッダを、クライアント側では document.cookie を利用します。 

  2. 単に State に clientId を保存するだけなので、 Store の実装は割愛します。 

  3. Stack Overflow の Q&A を参考にしています。 

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

気象庁の雨量データをスクレイピングしM5Stackで表示する

概要

  • 集合住宅では雨の様子が窓からは分からず、エントランスに出てから雨に気づき傘を取りに戻ることが良くあった
  • 一部のオフィスビル(30階建て以上)では、エレベーターホールに傘のサインで降水を知らせてくれていた
  • ベランダに降水センサーを設置しアラームを通知する方法もあるが、電源確保、センサーの耐久性の面から難しい
  • 気象庁の観測データページを、常時起動しているサーバー(Raspberry Pi)でスクレイピングした結果をM5Stackで表示する
    https://www.data.jma.go.jp/obd/stats/data/mdrr/pre_rct/alltable/pre1h00.html

IMG_9811.JPG

用意するもの

  • Raspberry Pi3 Model B (4でもおそらく大丈夫)
  • Raspberry Pi用のケース
  • Raspberry Pi用のヒートシンク
  • Raspberry Pi3 Model B B+ 対応 電源セット(5V 3.0A)
  • M5Stack
  • M5Stack用の電源
  • Arduino IDEが動くPC (Mac/Windows)

※サーバーは前回作成した来客通知システム(常時起動)を兼用する
https://qiita.com/cami_oshimo/items/b4e4002f7c47dd00ba84

環境構築

(1)Arduino IDEをインストール
https://www.arduino.cc/en/main/software
(2)M5Stack関連のライブラリをインストール
https://mag.switch-science.com/2018/02/28/getting-started-with-m5stack/
(3)Raspberri PiにApache(httpサーバー)をインストールしておく
※Raspberry Pi側のセットアップは割愛

準備

  • 気象庁の降水量のページをChromeのDeveloperToolで開き、該当地区の降水量が出力されている場所を確認する

例)広島市中区 ※私は広島市よりリモート勤務中です
スクリーンショット 2020-09-23 9.57.37.png

プログラミング

  • 常時起動したRaspberry PiでPythonを使ったコードでスクレイピングを行い index.html に出力する
  • M5Stackで index.html を表示する

メインプログラム
広島(ヒロシマ) を他地域に変更すればその土地の降水量が取得できます。

/var/www/html/rain.php
<?php

mb_language("Japanese");//処理言語の設定
mb_internal_encoding("UTF-8");//文字コードの指定
date_default_timezone_set('Asia/Tokyo');//timezoneをセット

$url = "http://www.data.jma.go.jp/obd/stats/data/mdrr/pre_rct/alltable/pre1h00.h
tml";

//webページの読み込み
$contents= file_get_contents($url);

//文字コードの変換
$contents_utf = mb_convert_encoding($contents,"UTF-8","auto");

//指定した文字列で囲まれた部分を取り出す。
$startString = '<td style="white-space:nowrap">広島(ヒロシマ)*</td><td style="
text-align:right;white-space:nowrap;">';
$endString = '</td>';

$startPoint = mb_strpos($contents_utf,$startString) ;//$startStringが現れる位置を調べる
$startPoint = $startPoint + mb_strlen($startString);//開始位置
//echo $startPoint;
//echo"";

$endPoint = mb_strpos($contents_utf,$endString,$startPoint );//$endStringが次に
現れる位置を調べる
//echo $endPoint;
//echo"";

$length = $endPoint - $startPoint;//文字列の長さを求める。
$rainfallString = mb_substr($contents_utf,$startPoint,$length)." mm"."\n\n"."GET
 Time"."\n".date("H:i:s")."\n";
echo $rainfallString;
file_put_contents("/var/www/html/index.html", $rainfallString);
?>

PHP実行用のシェルスクリプト

/var/www/html/cron.php
#!/bin/sh
/usr/bin/php /var/www/html/rain.php

PHP実行結果

/var/www/html/index.html
0.0 mm

GET Time
09:30:02

ブラウザからも見えることを確認する。
スクリーンショット 2020-09-23 9.37.08.png

自動起動の設定(この例では1時間毎に降水量を取得)
*/60 * * * * root sudo sh /var/www/html/cron.sh

M5Stack側のプログラミング

rain.ino
#include <M5Stack.h>
#include <WiFi.h>
#include <HTTPClient.h>

const char* ssid       = "XXXXXXXXXXXXX";  // Wifi SSID
const char* password   = "XXXXXXXXXXXXX";  // Wifi Password

const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;


void setup()
{
  M5.begin();
  M5.Lcd.setBrightness(10);
  M5.Lcd.printf("Connecting to %s ", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      M5.Lcd.printf(".");
  }

  M5.Lcd.println(" CONNECTED");
  M5.Lcd.fillScreen(BLACK);
}

void getrain()
{
  M5.Lcd.fillScreen(BLACK);
  HTTPClient http;

  http.begin("http://xxx.xxx.xxx.xxx/index.html");  // Raspberry PiのIPアドレス
  int httpCode = http.GET();

  String result = http.getString();
  M5.Lcd.setTextSize(5);
  M5.Lcd.setCursor(0,0);
  M5.Lcd.print(result);

  http.end();

}

void loop()
{
  getrain();
  delay(xxxxxxx);  // raspberryPiから `index.html` を取得する間隔(ミリ秒)
}

注意点

  • スクレイピングの頻度を上げすぎると相手側のサーバーの負荷となり、問題になるので気をつける

使ってみて

  • スマホアプリでも降水量が見られるが、玄関先などに置いておくと簡単に確認できるので便利
  • 当初さくらのVPS(Linux)で2年ほど稼働させていたが、月額固定費用がかかることもあり廃止し、Raspberry Piに移行した(1ヶ月程度経過したが問題なし)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP: 連想配列とDB操作(初心者向け)

初心者向けにDB接続の記事を書いたのですが、初心者の方にわからないと言われました笑
PHP: らくらくPDOクラス

ヒアリングをしてみると間に配列を挟んでるところがごちゃつくようです。

DB -> PDOでDBからデータGet -> なんかわからんけどforeach??

正解は「連想配列に一旦、格納する!!」です。

DB -> PDOでDBからデータGet -> 連想配列に格納 -> foreachで展開

配列変数 -> 1つの変数で複数の値を格納できるスグレモノ。

変数と配列変数の比較
普通の変数   //入る場所は一つ
$b = 1;
echo $b;    //-> 1

配列変数連想配列 //入る場所が複数, 添字キーで格納 key: value

//連想配列を1つつくってみます(生成): key => value
$bar = ['id' => 1, 'name' => 'CakePHP', 'birthday' => '2005-04-01'];

var_dump($bar);         //一度確認しましょう!

//呼び出し方
echo $bar['id'];        //-> 1
echo $bar['birthday'];  //-> 2000-02-03

//foreachで呼び出す
foreach ($bar as $val) {
    echo $val . ',';
}                       //-> 1,CakePHP,2000-02-03

多次元配列(配列の中に配列が入ります。)

多次元配列(連想配列)
データベースと連動してよく使われます
-> DBテーブル名 -> key
-> DBデータ   -> value

//多次元配列を生成してみます: key => value
$bars = [['id' => 1, 'name' => 'CakePHP', 'birthday' => '2005-04-01'],       //0
         ['id' => 2, 'name' => 'Laravel', 'birthday' => '2011-06-01'],       //1
         ['id' => 3, 'name' => 'Ruby on Rails', 'birthday' => '2004-07-01'], //2
];

var_dump($bars);             //必ず確認します!

呼び出し方
echo $bars[0]['birthday'];   //-> 2005-04-01
echo $bars[1]['name'];       //-> Laravel
echo $bars[2]['id'];         //-> 3

foreachで呼び出す: 
//1ループ目: $bar <- ['id' => 1, 'name' => 'CakePHP', 'birthday' => '2005-04-01']
//2ループ目: $bar <- ['id' => 2, 'name' => 'Laravel', 'birthday' => '2011-06-01']
//3ループ目: $bar <- ['id' => 3, 'name' => 'Ruby on Rails', 'birthday' => '2004-07-01']
foreach ($bars as $bar) {
    echo $bar['id'] . ':';
    echo $bar['name'] . ',';
}  //-> 1:CakePHP,2:Laravel,3:Ruby on Rails,

多重ループで全て呼び出す
//$bars -> [id, name, birthday] -> $bar 以下繰り返し
foreach ($bars as $bar) {
    foreach ($bar as $val) {
        echo $val . ',';
    }
    echo '<br>';
}  //-> 1,CakePHP,2005-04-01,
   //   2,Laravel,2011-06-01,
   //   3,Ruby on Rails,2004-07-01,

PDOとの連携

例の中で連想配列・多次元配列を生成している部分をPDOがやってくれます。
その後はforeachでデータをブン回して処理しましょ!

LGTMお願いします!
モチベーションがあがります!

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

CentOSでRabbitMQ+PHPを試す

PHPで非同期処理を導入するために、RabbitMQを試した際のメモ。
※この記事ではCentOS7、PHP7.3を使用しています。

MQの準備

RabbitMQインストール

yum --enablerepo=epel -y install rabbitmq-server
systemctl start rabbitmq-server
systemctl enable rabbitmq-server

ユーザ追加

rabbitmqctl add_user mquser password
rabbitmqctl list_users

バーチャルホスト追加

rabbitmqctl add_vhost myhost
rabbitmqctl list_vhosts

パーミッション設定

rabbitmqctl set_permissions -p myhost mquser ".*" ".*" ".*"
rabbitmqctl list_permissions -p myhost

PHPプログラムの準備(送信側&受信側)

phpライブラリインストール

yum -y install --enablerepo=epel,remi-php73 php php-bcmath composer

composerインストール

※一般ユーザにて実施

cd
mkdir mq
cd mq
composer require php-amqplib/php-amqplib
composer install

送信側

送信アプリ作成

vi send_msg.php
<?php
require_once __DIR__ . '/vendor/autoload.php';

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

$connection = new AMQPStreamConnection('127.0.0.1', 5672, 'mquser', 'password', 'myhost');

$channel = $connection->channel();
$channel->queue_declare('Hello_World', false, false, false, false);

$msg = new AMQPMessage('Hello RabbitMQ World!');
$channel->basic_publish($msg, '', 'Hello_World');
echo " [x] Sent 'Hello_World'\n";

$channel->close();
$connection->close();
?>

送信

php send_msg.php

受信側

受信アプリ作成

vi receive_msg.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;

$connection = new AMQPStreamConnection('127.0.0.1', 5672, 'mquser', 'password', 'myhost');
$channel = $connection->channel();

$channel->queue_declare('Hello_World', false, false, false, false);

echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";

$callback = function($msg) {
    echo " [x] Received ", $msg->body, "\n";
};

$channel->basic_consume('Hello_World', '', false, true, false, false, $callback);

while(count($channel->callbacks)) {
    $channel->wait();
}
?>

受信

php receive_msg.php
(CTRL+Cで停止)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CentOS8にベータ版php8.0をインストールする方法

経緯

php8.0が2020年11月26日にリリースされる予定らしい。
remi氏のリポジトリでもベータ版が出ているのでインストールして動作確認する。

インストール方法

百番煎じくらいの、CentOS8でPHP8環境作るコピペ
Centos8 php8.0をインストールする方法
php80-php-8.0.0~beta4-32.el8.remi.x86_64.rpm

phpの本体を入れる。

dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
dnf install yum-utils
dnf --enablerepo=remi install php80-php

データベースの接続も試したいのでmariadbを入れる

dnf install mariadb-server
systemctl start mariadb.service
mysql_secure_installation

phpのモジュールを入れる。

dnf install --enablerepo=remi php80-php-mysqlnd php80-php-mysqli php80-php-gd php80-php-cli php80-php-opcache
dnf install --enablerepo=remi php80-php-pecl-zip php80-php-devel php80-php-pear

php、pear、peclなどのコマンドはインストールするだけではパスが通らないので、findで探してフルパスで呼び出す。

find / -name pear
find / -name pecl
/opt/remi/php80/root/usr/bin/pecl install APCu
/opt/remi/php80/root/usr/bin/pear install Mail_MimeDecode
/opt/remi/php80/root/usr/bin/pear install HTTP_Client
/opt/remi/php80/root/usr/bin/pear install Net_IPv6

alternativesを利用してバージョンを切り替えると上品だけど、今回はテストなのでしない。
複数のバージョンの PHP を切り替えて使う

httpdとphpを起動。最近はphp-fpmが入るのが普通らしい。

systemctl start httpd.service
systemctl start php80-php-fpm.service

phpinfo();を実行し「Loaded Configuration File」を調べてphp.iniのパスを確認する。
php_ini.png
php.iniに追記。

php.ini
extension=apcu.so

phpを再起動。

systemctl restart php80-php-fpm.service

結果

gdやmysqliは異常なし。
OPcacheもapcuも普通に動いてる。
Net_IPv6がFatal errorを吐くので修正。

/opt/remi/php80/root/usr/share/pear/Net/IPv6.php
# 806行目付近
#            $ip{$pos} = '_'; #修正前
            $ip[$pos] = '_'; #修正後
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】Laravel 6でユーザー認証作る時につまづいて解決した方法共有!!

はじめに

こんばんは!
私は、毎週、「1週間1制作計画」っていうのをやっています。
最近は、PHPの勉強を始めたので、今週は、PHP及びLaravelの練習を兼ねてLaravelでアプリケーションを制作してみようと思っていました。

そこで、認証機能をつけようと思い、Laravelに標準装備されている認証機能Authを使ってみようと考え、トライしてみました!!

...すると、めっちゃつまづいた!!

今日の投稿では、そのつまづいた部分について、解決した方法を、認証機能の実装方法と共に記録していきます!
また、その方法自体は最後に記載されている参考資料・文献を参考に実装しました。

まずはLaravel 6をインストールしたい!!

Laravel 6をなぜ選んだかというと、サポート期間が最新のLaravel 8よりも長いためです(LTS = Long-Term-Support)。

一般的なインストール方法

laravel new プロジェクト名

ですよね!
しかし、今回は違います!以下のコードをご覧ください。

composer create-project "laravel/laravel=6.*" プロジェクト名

バージョンを指定して、Laravelをインストールする方法は、上記のように、composerを用います。
アプリケーションの実行には、通常通り、

php artisan serve

を使います。

標準装備Auth使っていきましょう!

それでは、いよいよ標準装備のAuthを使っていきます!
まずは、Auth機能を使うために、laravel/uiというパッケージを導入しましょう。

composer require laravel/ui:^1.0 --dev

上記コードをターミナルに入力して実行すると、認証部分のフロントエンドを制作できるlaravel/uiを導入することができます。
Auth関連のファイルを生成するために、以下の2つをターミナルに記述して実行します。

php artisan ui vue --auth
php artisan migrate

php artisan migrateしたらエラー返ってきた!!

そうです、このphp artisan migrateしたら、以下のようなエラーが返ってきてしまいました。

   Illuminate\Database\QueryException  : SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = homestead and table_name = migrations)

  at /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000] [2002] Connection refused")
      /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=127.0.0.1;port=3306;dbname=homestead", "homestead", "secret", [])
      /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.

正直、まだデータベースいじってないのに、何なんだ!!
「よくわからん!!調べても、Dockerを利用している人が多くて、自分の開発環境とは違うので参考にならん!!」
って感じでした。
なので、自分の手持ちのテキストに書いてあったように、そのまま以下をターミナルに記述して実行しちゃいました(笑)

npm install && npm run dev

そして、先ほど、アプリケーションを実行した時のように、

php artisan serve

を実行。
アプリケーションを開くことができる状態になります。
http://localhost:8000/
を開いてみてください。
スクリーンショット 2020-09-22 23.09.58.png
上の画像のようなページが開きます。最初に開いた時と変わらないようにも消えますが、
右上を見て頂くと、きちんと、LOGINとREGISTERと書かれていることがわかります。つまり、きちんと、Auth機能、実装できたんじゃないかと思いますよね!!
これが実は落とし穴...テキストでは触れられていない落とし穴だった。

REGISTER(ユーザー登録)できない!?

まず、初めて利用するので、ユーザー登録をしてみます。
REGISTERを押して、ユーザー登録画面に遷移します。
そして、ユーザー名やメールアドレス、パスワードと言った、登録に必要なデータを入力します。
さぁ、REGISTERボタンを押して、登録だ!

Database name seems incorrect
You're using the default database name laravel. This database does not exist.
Edit the .env file and use the correct database name in the DB_DATABASE key.

なんか出てきた...泣

先ほど、php artisan migrate失敗した時もデータベース関連のエラーが出てきましたね。
今回も同じく、データベース関連のエラーです。データベース名が違うんですって。(incorrect)

次項から、この問題を解決していきます。

まず、データベースに接続しよう!

Laravelプロジェクト作ってから全くいじっていなかったデータベースを、ついにいじっていきます!

MySQLをインストール

MySQLをまずはインストールしましょう。以下をターミナルに記述して実行してください。

brew install mysql

インストールできたら、起動してください。

mysql.server start --skip-grant-tables

上記の方法で起動すると、パスワード不要でした!
その後、ルートユーザーで接続。

mysql -uroot

データベースを作っていきましょう

それでは、続いて、データベースを作っていきます。
最初に、以下のコマンドを記述して、現在のデータベースの様子をチェックします。

mysql> show databases;

ダッシュ(途切れ途切れのあの線)で囲われて、Databaseっていうのが出てきます。出てきたらオッケーです。
次に、新しくデータベースを作成するコマンドを記述して実行してください。

create database sample;

私が、上記でsampleと記述した部分は、みなさんの任意の名前です。
そしてもう一度、show databases;で、データベースの状況を確認します。出力結果に、ここでいう、sampleが含まれていたら問題ないです!

rootユーザーのパスワードの変更

MySQLのrootユーザーのパスワードの変更を行います。

mysql> FLUSH PRIVILEGES;

mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'secret';

上記を実行してください。
それぞれ、実行直後には、Query OK, なんちゃらとかいうのが返ってきたらOKです!
ここでは、rootユーザーのパスワードを、「secret」に変更しています。
こちらも特に決まりはないので、任意の言葉で。
まぁ、サンプルコードでよく見るのは、secretですね(笑)

この処理が終了すれば、一旦MySQLを終了してから再起動させます。以下のコマンドを入力。

mysql> exit

再起動

mysql.server restart

認証プラグインの変更

ここで、すいません、MySQLをまた違う方法で開きたいので、またまたexitをお願いします。
以下のコマンドでMySQLに接続してください。

mysql -uroot -p

その後、Enter Passwordと言われ、パスワードの入力を求められます。
先ほど、変更したルートユーザーのパスワード、secretを入力してください。
接続できたら、次のコマンドを実行。

SELECT user, host, plugin FROM mysql.user;

SELECT文が出てきたら、ようやくデータベースいじってる感出てきましたねー!
上記コマンドで現状の認証プラグインを調べることができます。
おそらく、caching_sha2_passwordとなっているかと思います。
今回は、mysql_native_passwordを使いたいので、変更しましょう。以下のコマンドを実行してください。

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'secret';

それでは、再度、以下のコマンドで確認してみましょう。

SELECT user, host, plugin FROM mysql.user;

root項目のプラグインが、caching_sha2_passwordだったのに、mysql_native_passwordに変更されているかと思います。

あと少し!.envファイルの編集をしよう!

一旦、ターミナルからは離れて、エディタに移動してください。
作成したプロジェクトのフォルダ内に、「.env」という名前のファイルがあると思います。そのファイルを開きましょう。
※examapleがついている.envファイルは違います!!今回編集するのは、ただの.envファイルです!

開いたらなんだかデータベースに関連しそうな内容がたくさん書かれています。
その中でも、以下のコメントアウトした部分を変更します。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel // ココ変更するよ!
DB_USERNAME=root
DB_PASSWORD= // ココ変更するよ!

まず、DB_DATABASE=laravelの、laravelの部分を、sampleにします。
このsampleは、新しくデータベースを作った時に使った名前です。

DB_PASSWORD= は、元々空欄になっていますが、rootユーザーのパスワードとして設定した、secretを空欄部分に入れます。

最終的には、以下のような状態になるかと思います。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=SampleApp
DB_USERNAME=root
DB_PASSWORD=secret

編集後、またターミナルに帰りましょう。
キャッシュの削除を行います。

php artisan config:cache

最初できなかったphp artisan migrateしてみる!

この時点で、

php artisan migrate

してみると、成功します。

REGISTERリベンジ!

もう一度、Laravelアプリケーションを実行してみましょう。

php artisan serve

ですね。
そしてもう一度、REGISTERに挑戦してみてください!!
すると、エラーは特に出ず、登録できるようになっています!!

まとめ

長くなりましたが、数時間かけて格闘していたので、備忘録としてまとめてみました!
ここまでお読みくださり、ありがとうございました。
理解不足等による不備があれば、コメントお願い致します。

参考資料・文献

【Laravel】MySQLの接続方法を徹底解説【コピペでOK】

更新!!Laravel6/7 (laravel/ui)でのLogin機能の実装方法〜MyMemo

【Laravel】Laravelでバージョンを指定してインストールする方法

掌田津耶乃著『PHPフレームワーク Laravel入門 第2版』(2020)

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

【備忘録】Laravel 6でユーザー認証作る時につまづいたけど解決した方法共有!!

はじめに

こんばんは!
私は、毎週、「1週間1制作計画」っていうのをやっています。
最近は、PHPの勉強を始めたので、今週は、PHP及びLaravelの練習を兼ねてLaravelでアプリケーションを制作してみようと思っていました。

そこで、認証機能をつけようと思い、Laravelに標準装備されている認証機能Authを使ってみようと考え、トライしてみました!!

...すると、めっちゃつまづいた!!

今日の投稿では、そのつまづいた部分について、解決した方法を、認証機能の実装方法と共に記録していきます!
また、その方法自体は最後に記載されている参考資料・文献を参考に実装しました。

まずはLaravel 6をインストールしたい!!

Laravel 6をなぜ選んだかというと、サポート期間が最新のLaravel 8よりも長いためです(LTS = Long-Term-Support)。

一般的なインストール方法

laravel new プロジェクト名

ですよね!
しかし、今回は違います!以下のコードをご覧ください。

composer create-project "laravel/laravel=6.*" プロジェクト名

バージョンを指定して、Laravelをインストールする方法は、上記のように、composerを用います。
アプリケーションの実行には、通常通り、

php artisan serve

を使います。

標準装備Auth使っていきましょう!

それでは、いよいよ標準装備のAuthを使っていきます!
まずは、Auth機能を使うために、laravel/uiというパッケージを導入しましょう。

composer require laravel/ui:^1.0 --dev

上記コードをターミナルに入力して実行すると、認証部分のフロントエンドを制作できるlaravel/uiを導入することができます。
Auth関連のファイルを生成するために、以下の2つをターミナルに記述して実行します。

php artisan ui vue --auth
php artisan migrate

php artisan migrateしたらエラー返ってきた!!

そうです、このphp artisan migrateしたら、以下のようなエラーが返ってきてしまいました。

   Illuminate\Database\QueryException  : SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = homestead and table_name = migrations)

  at /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000] [2002] Connection refused")
      /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=127.0.0.1;port=3306;dbname=homestead", "homestead", "secret", [])
      /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.

正直、まだデータベースいじってないのに、何なんだ!!
「よくわからん!!調べても、Dockerを利用している人が多くて、自分の開発環境とは違うので参考にならん!!」
って感じでした。
なので、自分の手持ちのテキストに書いてあったように、そのまま以下をターミナルに記述して実行しちゃいました(笑)

npm install && npm run dev

そして、先ほど、アプリケーションを実行した時のように、

php artisan serve

を実行。
アプリケーションを開くことができる状態になります。
http://localhost:8000/
を開いてみてください。
スクリーンショット 2020-09-22 23.09.58.png
上の画像のようなページが開きます。最初に開いた時と変わらないようにも消えますが、
右上を見て頂くと、きちんと、LOGINとREGISTERと書かれていることがわかります。つまり、きちんと、Auth機能、実装できたんじゃないかと思いますよね!!
これが実は落とし穴...テキストでは触れられていない落とし穴だった。

REGISTER(ユーザー登録)できない!?

まず、初めて利用するので、ユーザー登録をしてみます。
REGISTERを押して、ユーザー登録画面に遷移します。
そして、ユーザー名やメールアドレス、パスワードと言った、登録に必要なデータを入力します。
さぁ、REGISTERボタンを押して、登録だ!

Database name seems incorrect
You're using the default database name laravel. This database does not exist.
Edit the .env file and use the correct database name in the DB_DATABASE key.

なんか出てきた...泣

先ほど、php artisan migrate失敗した時もデータベース関連のエラーが出てきましたね。
今回も同じく、データベース関連のエラーです。データベース名が違うんですって。(incorrect)

次項から、この問題を解決していきます。

まず、データベースに接続しよう!

Laravelプロジェクト作ってから全くいじっていなかったデータベースを、ついにいじっていきます!

MySQLをインストール

MySQLをまずはインストールしましょう。以下をターミナルに記述して実行してください。

brew install mysql

インストールできたら、起動してください。

mysql.server start --skip-grant-tables

上記の方法で起動すると、パスワード不要でした!
その後、ルートユーザーで接続。

mysql -uroot

データベースを作っていきましょう

それでは、続いて、データベースを作っていきます。
最初に、以下のコマンドを記述して、現在のデータベースの様子をチェックします。

mysql> show databases;

ダッシュ(途切れ途切れのあの線)で囲われて、Databaseっていうのが出てきます。出てきたらオッケーです。
次に、新しくデータベースを作成するコマンドを記述して実行してください。

create database sample;

私が、上記でsampleと記述した部分は、みなさんの任意の名前です。
そしてもう一度、show databases;で、データベースの状況を確認します。出力結果に、ここでいう、sampleが含まれていたら問題ないです!

rootユーザーのパスワードの変更

MySQLのrootユーザーのパスワードの変更を行います。

mysql> FLUSH PRIVILEGES;

mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'secret';

上記を実行してください。
それぞれ、実行直後には、Query OK, なんちゃらとかいうのが返ってきたらOKです!
ここでは、rootユーザーのパスワードを、「secret」に変更しています。
こちらも特に決まりはないので、任意の言葉で。
まぁ、サンプルコードでよく見るのは、secretですね(笑)

この処理が終了すれば、一旦MySQLを終了してから再起動させます。以下のコマンドを入力。

mysql> exit

再起動

mysql.server restart

認証プラグインの変更

ここで、すいません、MySQLをまた違う方法で開きたいので、またまたexitをお願いします。
以下のコマンドでMySQLに接続してください。

mysql -uroot -p

その後、Enter Passwordと言われ、パスワードの入力を求められます。
先ほど、変更したルートユーザーのパスワード、secretを入力してください。
接続できたら、次のコマンドを実行。

SELECT user, host, plugin FROM mysql.user;

SELECT文が出てきたら、ようやくデータベースいじってる感出てきましたねー!
上記コマンドで現状の認証プラグインを調べることができます。
おそらく、caching_sha2_passwordとなっているかと思います。
今回は、mysql_native_passwordを使いたいので、変更しましょう。以下のコマンドを実行してください。

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'secret';

それでは、再度、以下のコマンドで確認してみましょう。

SELECT user, host, plugin FROM mysql.user;

root項目のプラグインが、caching_sha2_passwordだったのに、mysql_native_passwordに変更されているかと思います。

あと少し!.envファイルの編集をしよう!

一旦、ターミナルからは離れて、エディタに移動してください。
作成したプロジェクトのフォルダ内に、「.env」という名前のファイルがあると思います。そのファイルを開きましょう。
※examapleがついている.envファイルは違います!!今回編集するのは、ただの.envファイルです!

開いたらなんだかデータベースに関連しそうな内容がたくさん書かれています。
その中でも、以下のコメントアウトした部分を変更します。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel // ココ変更するよ!
DB_USERNAME=root
DB_PASSWORD= // ココ変更するよ!

まず、DB_DATABASE=laravelの、laravelの部分を、sampleにします。
このsampleは、新しくデータベースを作った時に使った名前です。

DB_PASSWORD= は、元々空欄になっていますが、rootユーザーのパスワードとして設定した、secretを空欄部分に入れます。

最終的には、以下のような状態になるかと思います。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=SampleApp
DB_USERNAME=root
DB_PASSWORD=secret

編集後、またターミナルに帰りましょう。
キャッシュの削除を行います。

php artisan config:cache

最初できなかったphp artisan migrateしてみる!

この時点で、

php artisan migrate

してみると、成功します。

REGISTERリベンジ!

もう一度、Laravelアプリケーションを実行してみましょう。

php artisan serve

ですね。
そしてもう一度、REGISTERに挑戦してみてください!!
すると、エラーは特に出ず、登録できるようになっています!!

まとめ

長くなりましたが、数時間かけて格闘していたので、備忘録としてまとめてみました!
ここまでお読みくださり、ありがとうございました。
理解不足等による不備があれば、コメントお願い致します。

参考資料・文献

【Laravel】MySQLの接続方法を徹底解説【コピペでOK】

更新!!Laravel6/7 (laravel/ui)でのLogin機能の実装方法〜MyMemo

【Laravel】Laravelでバージョンを指定してインストールする方法

掌田津耶乃著『PHPフレームワーク Laravel入門 第2版』(2020)

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

【備忘録】Laravel 6でユーザー認証作る時につまずいたけど解決した方法共有!!

はじめに

こんばんは!
私は、毎週、「1週間1制作計画」っていうのをやっています。
最近は、PHPの勉強を始めたので、今週は、PHP及びLaravelの練習を兼ねてLaravelでアプリケーションを制作してみようと思っていました。

そこで、認証機能をつけようと思い、Laravelに標準装備されている認証機能Authを使ってみようと考え、トライしてみました!!

...すると、めっちゃつまづいた!!

今日の投稿では、そのつまづいた部分について、解決した方法を、認証機能の実装方法と共に記録していきます!
また、その方法自体は最後に記載されている参考資料・文献を参考に実装しました。

まずはLaravel 6をインストールしたい!!

Laravel 6をなぜ選んだかというと、サポート期間が最新のLaravel 8よりも長いためです(LTS = Long-Term-Support)。

一般的なインストール方法

laravel new プロジェクト名

ですよね!
しかし、今回は違います!以下のコードをご覧ください。

composer create-project "laravel/laravel=6.*" プロジェクト名

バージョンを指定して、Laravelをインストールする方法は、上記のように、composerを用います。
アプリケーションの実行には、通常通り、

php artisan serve

を使います。

標準装備Auth使っていきましょう!

それでは、いよいよ標準装備のAuthを使っていきます!
まずは、Auth機能を使うために、laravel/uiというパッケージを導入しましょう。

composer require laravel/ui:^1.0 --dev

上記コードをターミナルに入力して実行すると、認証部分のフロントエンドを制作できるlaravel/uiを導入することができます。
Auth関連のファイルを生成するために、以下の2つをターミナルに記述して実行します。

php artisan ui vue --auth
php artisan migrate

php artisan migrateしたらエラー返ってきた!!

そうです、このphp artisan migrateしたら、以下のようなエラーが返ってきてしまいました。

   Illuminate\Database\QueryException  : SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = homestead and table_name = migrations)

  at /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000] [2002] Connection refused")
      /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=127.0.0.1;port=3306;dbname=homestead", "homestead", "secret", [])
      /Users/username/code/LaravelSample/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.

正直、まだデータベースいじってないのに、何なんだ!!
「よくわからん!!調べても、Dockerを利用している人が多くて、自分の開発環境とは違うので参考にならん!!」
って感じでした。
なので、自分の手持ちのテキストに書いてあったように、そのまま以下をターミナルに記述して実行しちゃいました(笑)

npm install && npm run dev

そして、先ほど、アプリケーションを実行した時のように、

php artisan serve

を実行。
アプリケーションを開くことができる状態になります。
http://localhost:8000/
を開いてみてください。
スクリーンショット 2020-09-22 23.09.58.png
上の画像のようなページが開きます。最初に開いた時と変わらないようにも消えますが、
右上を見て頂くと、きちんと、LOGINとREGISTERと書かれていることがわかります。つまり、きちんと、Auth機能、実装できたんじゃないかと思いますよね!!
これが実は落とし穴...テキストでは触れられていない落とし穴だった。

REGISTER(ユーザー登録)できない!?

まず、初めて利用するので、ユーザー登録をしてみます。
REGISTERを押して、ユーザー登録画面に遷移します。
そして、ユーザー名やメールアドレス、パスワードと言った、登録に必要なデータを入力します。
さぁ、REGISTERボタンを押して、登録だ!

Database name seems incorrect
You're using the default database name laravel. This database does not exist.
Edit the .env file and use the correct database name in the DB_DATABASE key.

なんか出てきた...泣

先ほど、php artisan migrate失敗した時もデータベース関連のエラーが出てきましたね。
今回も同じく、データベース関連のエラーです。データベース名が違うんですって。(incorrect)

次項から、この問題を解決していきます。

まず、データベースに接続しよう!

Laravelプロジェクト作ってから全くいじっていなかったデータベースを、ついにいじっていきます!

MySQLをインストール

MySQLをまずはインストールしましょう。以下をターミナルに記述して実行してください。

brew install mysql

インストールできたら、起動してください。

mysql.server start --skip-grant-tables

上記の方法で起動すると、パスワード不要でした!
その後、ルートユーザーで接続。

mysql -uroot

データベースを作っていきましょう

それでは、続いて、データベースを作っていきます。
最初に、以下のコマンドを記述して、現在のデータベースの様子をチェックします。

mysql> show databases;

ダッシュ(途切れ途切れのあの線)で囲われて、Databaseっていうのが出てきます。出てきたらオッケーです。
次に、新しくデータベースを作成するコマンドを記述して実行してください。

create database sample;

私が、上記でsampleと記述した部分は、みなさんの任意の名前です。
そしてもう一度、show databases;で、データベースの状況を確認します。出力結果に、ここでいう、sampleが含まれていたら問題ないです!

rootユーザーのパスワードの変更

MySQLのrootユーザーのパスワードの変更を行います。

mysql> FLUSH PRIVILEGES;

mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'secret';

上記を実行してください。
それぞれ、実行直後には、Query OK, なんちゃらとかいうのが返ってきたらOKです!
ここでは、rootユーザーのパスワードを、「secret」に変更しています。
こちらも特に決まりはないので、任意の言葉で。
まぁ、サンプルコードでよく見るのは、secretですね(笑)

この処理が終了すれば、一旦MySQLを終了してから再起動させます。以下のコマンドを入力。

mysql> exit

再起動

mysql.server restart

認証プラグインの変更

ここで、すいません、MySQLをまた違う方法で開きたいので、またまたexitをお願いします。
以下のコマンドでMySQLに接続してください。

mysql -uroot -p

その後、Enter Passwordと言われ、パスワードの入力を求められます。
先ほど、変更したルートユーザーのパスワード、secretを入力してください。
接続できたら、次のコマンドを実行。

SELECT user, host, plugin FROM mysql.user;

SELECT文が出てきたら、ようやくデータベースいじってる感出てきましたねー!
上記コマンドで現状の認証プラグインを調べることができます。
おそらく、caching_sha2_passwordとなっているかと思います。
今回は、mysql_native_passwordを使いたいので、変更しましょう。以下のコマンドを実行してください。

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'secret';

それでは、再度、以下のコマンドで確認してみましょう。

SELECT user, host, plugin FROM mysql.user;

root項目のプラグインが、caching_sha2_passwordだったのに、mysql_native_passwordに変更されているかと思います。

あと少し!.envファイルの編集をしよう!

一旦、ターミナルからは離れて、エディタに移動してください。
作成したプロジェクトのフォルダ内に、「.env」という名前のファイルがあると思います。そのファイルを開きましょう。
※examapleがついている.envファイルは違います!!今回編集するのは、ただの.envファイルです!

開いたらなんだかデータベースに関連しそうな内容がたくさん書かれています。
その中でも、以下のコメントアウトした部分を変更します。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel // ココ変更するよ!
DB_USERNAME=root
DB_PASSWORD= // ココ変更するよ!

まず、DB_DATABASE=laravelの、laravelの部分を、sampleにします。
このsampleは、新しくデータベースを作った時に使った名前です。

DB_PASSWORD= は、元々空欄になっていますが、rootユーザーのパスワードとして設定した、secretを空欄部分に入れます。

最終的には、以下のような状態になるかと思います。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=SampleApp
DB_USERNAME=root
DB_PASSWORD=secret

編集後、またターミナルに帰りましょう。
キャッシュの削除を行います。

php artisan config:cache

最初できなかったphp artisan migrateしてみる!

この時点で、

php artisan migrate

してみると、成功します。

REGISTERリベンジ!

もう一度、Laravelアプリケーションを実行してみましょう。

php artisan serve

ですね。
そしてもう一度、REGISTERに挑戦してみてください!!
すると、エラーは特に出ず、登録できるようになっています!!

まとめ

長くなりましたが、数時間かけて格闘していたので、備忘録としてまとめてみました!
ここまでお読みくださり、ありがとうございました。
理解不足等による不備があれば、コメントお願い致します。

参考資料・文献

【Laravel】MySQLの接続方法を徹底解説【コピペでOK】

更新!!Laravel6/7 (laravel/ui)でのLogin機能の実装方法〜MyMemo

【Laravel】Laravelでバージョンを指定してインストールする方法

掌田津耶乃著『PHPフレームワーク Laravel入門 第2版』(2020)

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