20200921のPHPに関する記事は19件です。

PHP7の設定ファイルphp.iniの保管場所の探し方

PHPの設定を変更したかったのですが、とあるPHPの教科書(PHP7対応版)の書いてある通りやってもできずに時間をロスしてしまいました。今度そんなことがにないようにメモをしておきます!

その教科書には、アプリケーション→MAMP→conf→PHP7.x.x→php.iniを変更せよと書いてありますが、これは必ずしも正しくありません。少なくとも私の場合は上手くいきませんでした。
自分が使っているPHPがどのphp.iniを読み込んでいるかは、phpファイルの中に下記コードを書き込んでブラウザで読み込むと、インフォメーションが開いて確認できます。

index.php
<?php
phpinfo();
exit();//←こちらはお好みで
?>

開いたら、下記スクショのようにLoaded Configuration File確認できます。
スクリーンショット 2020-08-11 16.49.58.png

なので私の場合は/Applications/MAMP/bin/php7.x.x/conf/php.iniファイルを修正すれば良いということがわかります。

その教科書もディレクトリの場所をそのまま載せるのではなく、このようにどうやって設定ファイルの格納場所を調べればいいか教えてくれた方が良かったかもしれませんね。(って偉そうにすみません...)

もしご指摘あれば遠慮なくお願いします。

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

PHP7の設定ファイルの保管場所の探し方

PHPの設定を変更したかったのですが、とあるPHPの教科書(PHP7対応版)の書いてある通りやってもできずに時間をロスしてしまいました。今度そんなことがにないようにメモをしておきます!

その教科書には、アプリケーション→MAMP→conf→PHP7.x.x→php.iniを変更せよと書いてありますが、これは必ずしも正しくありません。少なくとも私の場合は上手くいきませんでした。
自分が使っているPHPがどのphp.iniを読み込んでいるかは、phpファイルの中に下記コードを書き込んでブラウザで読み込むと、インフォメーションが開いて確認できます。

index.php
<?php
phpinfo();
exit();//←こちらはお好みで
?>

開いたら、下記スクショのようにLoaded Configuration File確認できます。
スクリーンショット 2020-08-11 16.49.58.png

なので私の場合は/Applications/MAMP/bin/php7.x.x/conf/php.iniファイルを修正すれば良いということがわかります。

その教科書もディレクトリの場所をそのまま載せるのではなく、このようにどうやって設定ファイルの格納場所を調べればいいか教えてくれた方が良かったかもしれませんね。(って偉そうにすみません...)

もしご指摘あれば遠慮なくお願いします。

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

PHPにおける日付時刻の処理大全

日付時刻って重要だけど、そんなに頻度高くなくて、毎回調べちゃってたのでまとめておきます

目次

西暦から和暦へ変換
生年月日から年齢へ変換
日付が存在するか確認する
曜日の求め方
月末日の求め方
締め日の求め方
第三月曜日の求め方
年度の求め方
日付セレクトメニューの表示
日付の表示形式の変換と加減算
グレゴリウス日をユリウス積算日に変換

西暦から和暦へ変換

西暦が元号の期間に含まれているか確認し、改元された年から何年目なのか求める

元号 改元日
明治 1968/01/25
大正 1912/07/30
昭和 1926/12/25
平成 1989/01/08
令和 2019/05/01
example.php
<?php
$year = 2020;
$month = 10;
$day = 30;
$res = convertToJapaneseDate($year, $month, $day) ?? '不正な日時です';
echo $res;
//明治6年より前は返さない
function convertToJapaneseDate(int $year, int $month, int $day): string
{
    $REIWA = 20190501;
    $HEISEI = 19890108;
    $SHOWA = 19261225;
    $TAISHO = 19120730;
    //$MEIJI = 19680125;
    if (!checkdate($month, $day, $year) || $year < 1873) return false;
    $date = intval(sprintf('%04%02d%02d', $year, $month, $day));
    switch ($date) {
        case $date >= $REIWA:
            $era = '令和';
            $first_year = 2018;
            break;
        case $date >= $HEISEI:
            $era = '平成';
            $first_year = 1988;
            break;
        case $date >= $SHOWA:
            $era = '昭和';
            $first_year = 1925;
            break;
        case $date >= $TAISHO:
            $era = '大正';
            $first_year = 1911;
            break;
        default:
            $era = '明治';
            $first_year = 1868;
    }
    $year -= $first_year;
    $japanese_year = ($year == 1) ? $era . '元年' : $era . $year . '年';
    return $japanese_year . $month . '月' . $day . '日';
}

生年月日から年齢へ変換

example.php
floor((基準とする日付 - 生年月日) / 10000) //(int)/intval()でも可

これは日付を数値に変更する必要がある
たとえばexplode()関数を使うとかimplode()関数を使うとか、この二つを使えば大体なんとかなる
気が向いたら他の記事で解説したい笑
タイムスタンプから計算する場合は$birthday = (int)date('Ymd', $timestamp)みたいな感じかな

日付が存在するか確認する

example.php
checkdate(int $month, int $day, int $year): boolean

第三引数の年は1~32767まで

曜日の求め方

getDate()date()を使う
0が日曜で6が土曜

getdate.php
<?php
$timestamp = mktime(0, 0, 0, 10, 1, 2020);
$date = getdate($timestamp);
$wday = $date['wday'];

$weekday_label = ['日', '月', '火', '水', '木', '金', '土',];
echo date('Y/m/d', $timestamp); //2020/10/01
echo $wday . '(' . $weekday_label[$wday] . ')'; //1(木)
date.php
<?php
$timestamp = mktime(0, 0, 0, 10, 1, 2020);
$wday = intval(date('w', $timestamp));
echo $wday; //4

月末日の求め方

mktime()date()を使う
mktime()の場合
年に取得したい年月の年、
月に取得したい年月の月+1、
日には0を指定する

mktime.php
<?php
$year = 2020;
$month = 12;
$timestamp = mktime(0, 0, 0, $month + 1, $year);
echo $year . '年' . $month . '月の末日' . date('Y/m/d', $timestamp);

date()の場合
第一引数にtを指定する
戻り値を文字列で返すのでintval()で数値に変換する必要がある

date.php
<?php
$year = 2018;
$month = 4;
//2018/04/10のタイムスタンプ
$timestamp = mktime(0, 0, 0, $month, 10, $year);
//第一引数のtで第二引数で指定した月の日数の取得
$last_day = intval(date('t', $timestamp));
echo $year . '年' . $month . '月の末日' . $last_day;

締め日の求め方

example.php
<?php
$cutoffDay = 25;
$date = mktime(0, 0, 0, 10, 30, 2020);
echo '締め日' . $cutoffDay;
echo '計算したい日' . date('Y/m/d', $date);

$ret = getCutoffDate($cutoffDay, $date);
echo $ret == true ? date('Y/m/d', $ret) : 'invalid date';
//第一引数に締め日を指定(1~31)
////第二引数には計算したい日のタイムスタンプ省略した場合は現在の日時を指定
function getCutoffDate(int $cutoffDay, int $timestamp = null): int
{
    $timestamp = $timestamp ?? time();
    if ($cutoffDay < 1 || $cutoffDay > 31) return false;
    $date = getdate($timestamp);
    $year = $date['year'];
    $month = $date['mon'];
    $day = $date['mday'];
    $endOfMonth = intval(date('t', $timestamp));

    $cutoffDay = modifyCutoffDay($endOfMonth, $cutoffDay);
    //計算した日が締め日を過ぎている場合翌月の締め日を取得
    if ($day > $cutoffDay) {
        $month++;
        //締め日と翌月末日を比較
        $endOfNextMonth = intval(date('t', mktime(0, 0, 0, $month, 1, $year)));
        $cutoffDay = modifyCutoffDay($endOfNextMonth, $cutoffDay);
    }
    return mktime(0, 0, 0, $month, $cutoffDay, $year);
}
//月末日が締め日よりも前の場合月末日を締め日にする
//min()関数を使って値の判定と代入を行う
function modifyCutoffDay(int $endOfMonth, int $cutoffDay): int
{
    return min($endOfMonth, $cutoffDay);
}

第三月曜日の求め方

第一月曜日を求めその三週間後を求めればいい

example.php
<?php
$year = 2020;
$month = 10;
$week = 3;
$weekday = 1; //Monday
$res = getNthDay($year, $month, $week, $weekday) . '日' ?? '該当する日にちはありませんでした';
echo $res;
//第X番目のX曜日の日付を返す
//第三引数には週番号、第四引数には曜日を数値で指定
function getNthDay(int $year, int $month, int $week, int $weekday): int
{
    if ($week < 1 || $week > 5) return false;
    if ($weekday < 0 || $weekday > 6) return false;
    //月初の曜日を取得
    $firstWeekday = intval(date('w', mktime(0, 0, 0, $month, 1, $year)));
    //第一の日付を求める
    $firstDay = $weekday - $firstWeekday + 1;
    if ($firstDay <=  0) $firstDay += 7;
    //7の倍数を加算する
    $targetDay = $firstDay + 7 * ($week - 1);
    //存在する日付か確認
    if (!checkdate($month, $targetDay, $year)) return false;
    return $targetDay;
}

年度の求め方

example.php
<?php
$time = mktime(0, 0, 0, 10, 30, 2020);
$startMonth = 4;
$res = getYear($startMonth, $time) . '年度' ?? 'エラー';
echo $res;

function getYear(int $startMonth = 4, int $time = null): int
{
    if ($startMonth < 1 || $startMonth > 12) return false;
    $date = ($time == null) ? getdate() : getdate($time);
    $year = $date['year'];
    $month = $date['mon'] - ($startMonth - 1);
    $res = getdate(mktime(0, 0, 0, $month, 1, $year));
    return $res['year'];
}

日付セレクトメニューの表示

selectYear.php
<?php
$from = intval(date('Y'));
$to = $from + 5; //5years
//<select>
for ($i = $from; $i <= $to; $i++):
//<option value=\"<?= $i\; ?>"><?= $i; ?></option>
endfor;
//</select>年
selectYearMonth.php
<?php
$from = mktime(0, 0, 0, 10, 1, 2020);
$to = mktime(0, 0, 0, 10, 1, 2030);
//<select>
while ($from <= $to) {
    $label = date('Y年 m月', $from);
    $value = date('Ym', $from);
    //<option value=\"<?= $value\; ?>"><?= $label; ?></option>
    $date = getdate($from);
    $from = mktime(0, 0, 0, $date['mon'] + 1, 1, $date['year'];
}
//</select>

日付の表示形式の変換と加減算

DateTimeクラスを使うとインスタンスを作成し、format()メソッドで指定したフォーマット文字列に基づいて日時文字列を取得できる

example.php
<?php
$now = new DateTime();
$now->add(DateInterval::createFromDateString('任意の数 加減算する単位')); //ex) 1 day , 1 year
echo $now->format('表示形式'); //Y-m-d H:i:sなど
//n,j=1, m,d=01, H=23, h=11
$past = new DateTime('2020-10-30');
$past->getTimestamp();
$past->add(DateInterval::createFromDateString('1 day'));
echo $past->format('Y/m/d');

グレゴリウス日をユリウス積算日に変換

gregoriantojd(int $month, int $day, int $year): intを使う
ユリウス積算日をユリウス暦に変換するときはjdtojulian(int $julianday) : string

example.php
<?php
$jd = gregoriantojd(10, 11, 1970); //2440871
$gregorian = jdtogregorian($jd); //10/11/1970

intval()関数の注意

注意
整数型への変換が失敗した際に0や1を返してしまうことがある
全て文字列だった場合 0が返される
配列だった場合には要素が存在する場合には1空配列の場合には0が返される
真偽値だった場合はtrue=1, false=0が返される
もちろんfloat型の場合は切り捨てられる

数値型だった場合のみ変換したいときはis_numericで判定を行うといい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PHP] ローカルサーバーの立てかた

概要

ここまでphpをterminalで走らせていたが、実際の使用ではブラウザ上で処理、表示を行うケースが圧倒的に多いのでローカルサーバーの建て方をメモる。

プログラム

greet.php
<?php
  date_default_timezone_set('Asia/Tokyo');
  $now = date("Y/m/d H:i:s:");
  $now_hour = (int)date(G);

  function aisatsu($greet) {
    if ($now_hour >= 6 && $now_hour < 12) {
      $greet = 'おはようございます';
    } elseif ($now_hour >= 12 && $now_hour < 18) {
      $greet = 'こんにちは';
    } else {
      $greet = 'こんばんは';
    }
    return $greet;
  }

  $aisatsu = aisatsu($greet)
 ?>

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>タイトル</title>
    </head>
    <body>
        <p><?php print $aisatsu; ?></p>
    </body>
</html>
  date_default_timezone_set('Asia/Tokyo');

で取得した現在時刻に合わせて「おはよう」「こんにちは」「おやすみなさい」のどれか一つを表示するプログラム。

webサーバーを立てる

1. phpビルトインのローカルサーバーを起動する

実行したいphpファイルが入っているディレクトリまでterminalで移動し、下記を実行する

$ php -S localhost:8000

右端の数値は 8000 にしておく

2. ブラウザで表示する

検索バーに

http://localhost:8000/greet.php

と打つだけ

表示結果

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

簡易LISP処理系の実装例(PHP版)

【他言語版へのリンク記事】簡易LISP処理系の実装例【各言語版まとめ】

この記事は,下記拙作記事のPHP版を抜粋・修正したものを利用した,簡易LISP処理系("McCarthy's Original Lisp")の実装例をまとめたものです.

最低限の機能をもったLISP処理系の実装の場合,本体である評価器(eval)実装はとても簡単であり,むしろ,字句・構文解析を行うS式入出力やリスト処理実装の方が開発言語ごとの手間が多く,それが敷居になっている人向けにまとめています.

処理系の概要

実行例は次の通り.PHP 7.4.10のREPLにて確認.

$ php -a
Interactive shell

php > include "jmclisp.php";
php > echo s_rep("(car (cdr '(10 20 30)))");
20
php > echo s_rep("((lambda (x) (car (cdr x))) '(abc def ghi))");
def
php > echo s_rep("((lambda (f x y) (f x (f y '()))) 'cons '10 '20)");
(10 20)
php > echo s_rep("((lambda (f x y) (f x (f y '())))
php "              '(lambda (x y) (cons x (cons y '())))
php "              '10 '20)");
(10 (20 ()))
php > echo s_rep("((lambda (assoc k v) (assoc k v))
php "             '(lambda (k v)
php "                (cond ((eq v '()) nil)
php "                      ((eq (car (car v)) k)
php "                       (car v))
php "                      ('t (assoc k (cdr v)))))
php "             'Orange
php "             '((Apple . 120) (Orange . 210) (Lemmon . 180)))");
(Orange . 210)

実装内容は次の通り.

  • "McCarthy's Original Lisp"をベースにした評価器
  • 数字を含むアトムは全てシンボルとし,変数の値とする場合はquote')を使用
  • 構文としてquoteの他,condlambdaが使用可能
  • 組込関数:atom eq cons car cdr(内部でコンスセルを作成)
  • 真偽値はt(真)およびnil(偽)=空リスト=NULL
  • エラーチェックなし,モジュール化なし,ガーベジコレクションなし

"McCarthy's Original Lisp"の詳細についてはまとめ記事を参照.ダイナミックスコープということもあり,実行例ではlambda式をletrec(Scheme)やlabels(Common Lisp)などの代わりに使用しています.

実装例

ソースコード一式

jmclisp.php
<?php
#
# JMC Lisp: defined in McCarthy's 1960 paper,
# with S-expression input/output and basic list processing
#


# basic list processing: cons, car, cdr, eq, atom

function cons($x, $y) { return [$x, $y]; }
function car($s) { return $s[0]; }
function cdr($s) { return $s[1]; }
function eq($s1, $s2) { return $s1 === $s2; }
function atom($s) {
  return is_string($s) || is_bool($s) || is_null($s);
}


# S-expression output: s_string

function s_strcons($s)
{
  $sa_r = s_string(car($s));
  $sd = cdr($s);
  if (eq($sd, NULL)) {
    return $sa_r;
  } elseif (atom($sd)) {
    return $sa_r . " . " . $sd;
  } else {
    return $sa_r . " " . s_strcons($sd);
  }
}

function s_string($s)
{
  if     (eq($s, NULL))  { return "()";  }
  elseif (eq($s, true))  { return "t";   }
  elseif (eq($s, false)) { return "nil"; }
  elseif (atom($s)) {
    return $s;
  } else {
    return "(" . s_strcons($s) . ")";
  }
}


# S-expression input: s_read

function s_lex($s)
{
  return
    array_values(
      array_filter(
        explode(" ",
          str_replace([ "(" , ")" , "'" ,"\n"],
                      [" ( "," ) "," ' ", "" ],
                      $s))));
}

function s_syn_q($x, &$s)
{
  if (count($s) != 0 && end($s) == "'") {
    array_pop($s);
    return cons("quote", cons($x, NULL));
  } else {
    return $x;
  }
}

function s_syn(&$s)
{
  $t = array_pop($s);
  if ($t == ")") {
    $r = NULL;
    while (end($s) != "(") {
      if (end($s) == ".") {
        array_pop($s);
        $r = cons(s_syn($s), car($r));
      } else {
        $r = cons(s_syn($s), $r);
      }
    }
    array_pop($s);
    return s_syn_q($r, $s);
  } else {
    return s_syn_q($t, $s);
  }
}

function s_read($s) { return s_syn(s_lex($s)); }


# JMC Lisp evaluator: s_eval

function caar($x) { return car(car($x)); }
function cadr($x) { return car(cdr($x)); }
function cadar($x) { return car(cdr(car($x))); }
function caddr($x) { return car(cdr(cdr($x))); }
function caddar($x) { return car(cdr(cdr(car($x)))); }

function s_null($x) { return eq($x, NULL); }

function s_append($x, $y)
{
  if (s_null($x)) { return $y; }
  else { return cons(car($x), s_append(cdr($x), $y)); }
}

function s_list($x, $y) { return cons($x, cons($y, NULL)); }

function s_pair($x, $y)
{
  if (s_null($x) && s_null($y)) { return NULL; }
  elseif (!atom($x) && !atom($y)) {
    return cons(s_list(car($x), car($y)),
                s_pair(cdr($x), cdr($y)));
  }
}

function s_assoc($x, $y)
{
  if (eq(caar($y), $x)) {
    return cadar($y);
  } else {
    return s_assoc($x, cdr($y));
  }
}

function s_eval($e, $a)
{
    if     (eq($e, "t"))   { return true; }
    elseif (eq($e, "nil")) { return false; }
    elseif (atom($e))      { return s_assoc($e, $a); }
    elseif (atom(car($e))) {
        if     (eq(car($e), "quote")) { return cadr($e); }
        elseif (eq(car($e), "atom"))  { return atom(s_eval(cadr($e), $a)); }
        elseif (eq(car($e), "eq"))    { return eq(  s_eval(cadr($e), $a),
                                                    s_eval(caddr($e), $a)); }
        elseif (eq(car($e), "car"))   { return car( s_eval(cadr($e), $a)); }
        elseif (eq(car($e), "cdr"))   { return cdr( s_eval(cadr($e), $a)); }
        elseif (eq(car($e), "cons"))  { return cons(s_eval(cadr($e), $a),
                                                    s_eval(caddr($e), $a)); }
        elseif (eq(car($e), "cond"))  { return evcon(cdr($e), $a); }
        else { return s_eval(cons(s_assoc(car($e), $a), cdr($e)), $a); }
    }
    elseif (eq(caar($e), "lambda")) {
      return s_eval(caddar($e),
                    s_append(s_pair(cadar($e), evlis(cdr($e), $a)), $a));
    } else { print("Error"); }
}

function evcon($c, $a)
{
    if (s_eval(caar($c), $a)) { return s_eval(cadar($c), $a); }
    else { return evcon(cdr($c), $a); }
}

function evlis($m, $a)
{
    if (s_null($m)) { return NULL; }
    else { return cons(s_eval(car($m), $a), evlis(cdr($m), $a)); }
}


# REP (no Loop): s_rep
function s_rep($e) { return s_string(s_eval(s_read($e), "()")); }

解説

  • リスト処理:cons car cdr eq atom,S式出力:s_string
    先の記事よりそのまま抜粋.空リストは内部ではNULLを設定.

  • S式入力:s_read
    新規に作成.字句解析部s_lex()および'の識別でひとつの文字列を配列化.空白要素はarray_filterによって除去されるが,連想配列としての添字対応は変わらないので,array_valuesで添字振り直し.抽象構文木生成部s_synは括弧ネスト・ドット対・クォート記号対応とし,リスト処理関数でコンスセルによる構文木を生成.それらをまとめたS式入力関数s_readを定義.

  • 評価器:s_eval+ユーティリティ関数
    "McCarthy's Original Lisp"をベースにs_eval関数およびユーティリティ関数を作成.

  • REP (no Loop):s_rep
    s_reads_evals_stringをまとめたs_repを定義.

備考

更新履歴

  • 2020-09-21:初版公開
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】投稿種類別で投稿を出力してくれる機能

PHPについて学習内容を備忘録としてまとめます。
投稿種類別で投稿を出力してくれる機能を実装しましたので記載します。

アニメーション.gif

投稿を種類別で表示

まずは表示したいページに下記コードを書きます。

<?php
$page_type = $_GET['type'];
//$_SESSION['staff_code']は現在ログインしているユーザーのIDを取得しています
print'<a href="staff_top.php?staff_code='.$_SESSION['staff_code'].'&type=main">自分の投稿</a>';
print'<a href="staff_top.php?staff_code='.$_SESSION['staff_code'].'&type=favorites">いいねした投稿</a>';

$current_user=get_user($_SESSION['staff_code']);

switch ($page_type) {
  case 'main':
    $posts = get_posts($current_user['code'],'my_post',0);
  break;

  case 'favorites':
    $posts = get_posts($current_user['code'],'favorite',0);
  break;
}

<?php foreach($posts as $post): ?>

    <?php print'<br />'; ?>
    <?php print ''.$post['name'].''; ?>

<?php endforeach ?>

自分の投稿いいねした投稿をリンク表示させて現在のパスを指定します。
そして&以降にtypeを指定し投稿別で種類を分けています。
パスの中のtypeの値は$page_typeに渡しています。

パス中の?や&については、【PHP】リンクの?と& を参照ください。

<?php
$page_type = $_GET['type'];
//$_SESSION['staff_code']は現在ログインしているユーザーのIDを取得しています
print'<a href="staff_top.php?staff_code='.$_SESSION['staff_code'].'&type=main">自分の投稿</a>';
print'<a href="staff_top.php?staff_code='.$_SESSION['staff_code'].'&type=favorites">いいねした投稿</a>';

typeの値は$page_typeに渡されているので、この値ごとに$postsに渡す値を分けています。
get_posts関数についてはまた別の処理なので説明していきます。

get_posts関数

少し長い関数にはなっていますがやっていることとしては、
受け取った$page_typeによってspl文を変更し取得する投稿種類を判別する関数です。

function get_posts($page_id,$type){
  try {
    $dsn='mysql:dbname=shop;host=localhost;charset=utf8';
    $user='root';
    $password='';
    $dbh=new PDO($dsn,$user,$password);
    switch ($type) {
      case 'my_post':
      $sql = "SELECT mst_staff.code,mst_staff.name,mst_staff.password,mst_staff.delete_flg,mst_product.code,mst_product.name,mst_product.address,mst_product.time_start,mst_product.time_end,mst_product.gazou,mst_product.user_id
              FROM mst_staff INNER JOIN mst_product ON mst_staff.code = mst_product.user_id
              WHERE user_id = :id AND delete_flg = 0";
      break;

      case 'favorite':
      $sql = "SELECT mst_staff.code,mst_staff.name,mst_staff.password,mst_staff.delete_flg,mst_product.code,mst_product.name,mst_product.address,mst_product.time_start,mst_product.time_end,mst_product.gazou,mst_product.user_id
              FROM mst_product INNER JOIN favorite ON mst_product.code = favorite.post_id
              INNER JOIN mst_staff ON mst_staff.code = mst_product.user_id
              WHERE favorite.user_id = :id";
      break;

    }
    $stmt = $dbh->prepare($sql);
    $stmt->bindValue(':id', $page_id);
    $stmt->execute();
    return $stmt->fetchAll();
  } catch (\Exception $e) {
    error_log('エラー発生:' . $e->getMessage());
    set_flash('error',ERR_MSG1);
  }
}

get_posts関数の引数である$page_idが現在のユーザーID、$type$page_typeの値になります。
$page_typeの値よりSQL文を判別して該当の投稿を出力しています。

favoriteテーブルは投稿をいいねした際にいいねした人いいねした投稿を紐づけてくれるテーブルです。
詳しくはこちらで説明していますのでご参考ください。
【PHP】いいね機能実装

投稿種類別に投稿表示

投稿種類別に投稿を出力できるようになったので、実際に表示させます。
一番初めに記載したコードの中にあった下記の記述により投稿を表示させることができます。

<?php foreach($posts as $post): ?>

    <?php print'<br />'; ?>
    <?php print ''.$post['name'].''; ?>

<?php endforeach ?>

上記の処理は$postsの配列の中にある分だけ値を取り出します。
これで表示させるところまでできました。

参考URL

https://qiita.com/nyann123/items/7320d98d17768986add0

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

Heroku(Laravel)で自作アプリをHerokuにデプロイしようとしたらマイグレーションエラーが起きたので、ClearDB(MySQL5.5)からJawsDB(MySQL8.0)に変更した時の備忘録

開発環境

PHP7.3
Laravel5.8
MySQL8.0
Heroku使用時のDBアドオン:ClearDB MySQL5.5

やろうとしたこと

下記をheroku run php artisan migrate

<?php

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

class CreateChatRoomsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('chat_rooms', function (Blueprint $table) {
            $table->id();
            $table->text('user_info_json')->default(new Expression('(JSON_ARRAY())'));
            $table->timestamps();
        });
    }

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

エラーが出現。

エラーの内容

  SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(JSON_ARRAY()),   
  `created_at` timestamp null, `updated_at` timestamp null) defaul' at line 1 (SQL: create table `chat_rooms` (`id` bigint unsigned not null auto_increment primary key, `user_info_json` text not null default (  
  JSON_ARRAY()), `created_at` timestamp null, `updated_at` timestamp null) default character set utf8mb4 collate 'utf8mb4_unicode_ci')                                                                                                                                                                                                                                                                                             
  SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(JSON_ARRAY()),   
  `created_at` timestamp null, `updated_at` timestamp null) defaul' at line 1

推測

$table->text('user_info_json')->default(new Expression('(JSON_ARRAY())'));

の部分がSQLのバージョンに対応していないらしい。

ローカル(MySQL8.0)ではエラーは起こらなかったため、本番環境のSQLをバージョンアップすれば通るかも?

ClearDBからJawsDBにアドオンを変更しよう

ClearDB・・・MySQL5.5、5.6、5.7対応
JawsDB・・・5.7及び8.0対応
とのことであったため、ローカル環境(MySQL8.0)と合わせるべくJawsDBにアドオンを変更することに。

JawsDBアドオン取得

heroku addons:create jawsdb:[プラン名] -a [アプリ名] --version=8.0

--version=8.0のようにすることで、バージョンを8.0指定でインストール。

ClearDBの環境変数を削除

JawsDBをインストールしただけの状態なので、まだClearDB使用時の環境変数が登録されている。

DB_DATABASE:           [ClearDBのDATABASE]
DB_HOST:               [ClearDBのHOST]
DB_PASSWORD:           [ClearDBのPASSWORD]
DB_USERNAME:           [ClearDBのUSERNAME]

これらを全て一旦削除

heroku config:unset DB_DATABASE
heroku config:unset DB_HOST
heroku config:unset DB_PASSWORD
heroku config:unset DB_USERNAME

その後

heroku config

すると、ClearDBの環境変数が消えている。

JawsDBの接続情報を確認

heroku config |grep JAWSDB_URL
JAWSDB_URL:       mysql://[DB_USERNAME]:[DB_PASSWORD]@[DB_HOST]/[DB_DATABASE]

もしくはHerokuのマイページからJawsDBアドオンに遷移して確認。

JawsDBの環境変数登録

heroku config:set DB_DATABASE=[DB_DATABASE]
heroku config:set DB_HOST=[DB_HOST]
heroku config:set DB_PASSWORD=[DB_PASSWORD]
heroku config:set DB_USERNAME=[DB_USERNAME]

再度マイグレーション

heroku run php artisan migrate

通った!

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

vagrant upが実行できないとき

vagrant upで起動しようとしてもできないときがありました。

$vagrant up

エラー内容

A Vagrant environment or target machine is required to run this command. Run vagrant init to create a new Vagrant environment. Or, get an ID of a target machine from vagrant global-status to run
this command on. A final option is to change to a directory with a Vagrantfile and to try again.

解決策

$cd homestead 
$vagrant up

Homesteadを使ってlaravelの環境構築していたのですが、 Homesteadに移動していなかったので起動できなかったです。。。

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

画像アップロード掲示板 エラー処理

エラー処理の実装方法

$errors = array();
if(!strlen($body)) {
$errors[] = 'ひとことを入力してください';
}elseif(mb_strlen($body) > 200) {
$errors[] = 'ひとことは200文字以内で入力してください';
}
if(!is_uploaded_file($_FILES["upimg"]["tmp_name"])) {
$errors[] = '画像を選択してください'; }


if(count($errors) === 0) {
$uploads_dir = '/var/www/application/images/'; $tmp_name = $_FILES["upimg"]["tmp_name"];
$name = basename($_FILES["upimg"]["name"]); move_uploaded_file($tmp_name, $uploads_dir.$name);
chmod($uploads_dir.$name, 0677); $user = $this->session->get('user');
$this->db_manager->get('Status')->insert($user['id'], $body); $this->db_manager->get('Uploaders')->insert($user['user_name']);
return $this->redirect('/'); }

コード説明

$errors = array();

配列で$errorsに渡してあげる

if(!strlen($body)) {
$errors[] = 'ひとことを入力してください';
}elseif(mb_strlen($body) > 200) {
$errors[] = 'ひとことは200文字以内で入力してください'; }
if(!is_uploaded_file($_FILES["upimg"]["tmp_name"])) { $errors[] = '画像を選択してください';
}

もし$body(投稿内容)が空であれば、「ひとことを入力してください」と表示し、投稿
内容が200文字以上の場合は「ひとことは200文字以内で入力してください」と表示す る。もしアップロードされた画像がなかったら、「画像を選択してください」と表示する。

if(count($errors) === 0) {
$uploads_dir = '/var/www/application/images/'; $tmp_name = $_FILES["upimg"]["tmp_name"];
$name = basename($_FILES["upimg"]["name"]);
move_uploaded_file($tmp_name, $uploads_dir.$name);
chmod($uploads_dir.$name, 0677); }

もしエラーがゼロであれば、画像を保存する
・アップロードした画像を保存するためのディレクトリを指定
・サーバーに一時保管するためのファイル名
(upimgというのは、inputで自分が指定した名前を入れる)
・ファイル名
・ アップロードした画像をtmp_nameから$uploads_dirに移動
・画像ファイルのアクセス権限を指定

エラー処理の書く場所に注意したい。
私は、エラー処理は正しく書けていたが、プログラムの書く場所を間違っていたため、正
しく反映されなかった。
プログラムは上から下に順番に実行されていくことをよく頭に入れておく。

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

git add コマンドの使い方

$ git add .

このコマンドを打つと、コミット前に自分が修正したファイルを一時的に保存しておくことができます。

addの後ろにある「.」を付けることで、例えば修正したファイルが3つあれば、 全て保存することができる。

5つのファイルを修正して、そのうち2つだけコミットしたいときなどは、
「git add ファイル名」にすれば、指定したファイルのみ保存することができる。

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

laravelのマイグレーションでテーブル作成

1「php artisan make:migration create_flights_table」
こちらのコマンドを打ってマイグレーションを作成します。

2 create_flights_table.php ファイルが作成されてることを確認します。

3 そのファイルの中身を見てみると、upメソッドとdownメソッドがあります。 upメソッドは、新規作成するテーブルの中身を書いていきます。
downメソッドは、マイグレーションを元に戻す時に実行されます。特に何か弄る必 要はないです。

public function up() {
Schema::create('flights', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('airline');
            $table->timestamps();
        });
}

4 さっそくカラムを追加していきましょう。追加の仕方は、画像にある通りです。

Laravelでは、主キーである“ id ” に対してbigIncrementsというカラムタイプを指定してあげる必要があります。外部キーを設置する場合は、unsignedBigInteger を指定してあげます。

5 カラムが追加できたら、「php artisan migrate」でマイグレーションが実行され、 テーブルが作成されます。

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

ちょいとPHPを始めました。

この記事について

始めてのPHPで学べたことをまとめた記事。
本当の学びたて。

環境

PHP 7.3
Docker

文字列出力

echoを使った文字列出力

index.php
<?php

$message = "はじめてのPHP";


?>

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>練習</title>
  <link rel="icon" href="favicon.ico">
  <meta name="description">
  <link rel="stylesheet" href="css/styles.css">
</head>
<body>
  <header>
    <p><?php echo $message?></p>
  </header>
</body>
</html>

echoを使わずに省略して文字列を出力させる

index.php
<p><?php echo $message?></p>

<!-- 省略Ver.  -->
<p><?= $message?></p>

悪意のあるコードの実行させないようにさせる。
常にhtmlspecialchars()を使うことを意識して開発を行うのがいいみたい。

index.php
$message = "<script>alert(悪意のデータ);</script>こんにちは";
<p><?= htmlspecialchars($message, ENT_QUOTES, "UTF-8"); ?></p>

ファイル読み込み

index.php
<?php
include("_header.php");
?>

  <h1>こんにちは</h1>

<?php
include("_footer.php");
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

404エラーが出る原因とは

404とは、”存在しないページを表示させようとしている状態” のことです。

原因は2つあります。
・ URLに指定している変数が渡されてないこと
・ 表示しようとしてるページのURL自体が定義されてないこと

これらを確認することで、どこでミスってるのかを探せるといいです。

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

【オブジェクト指向】懐疑派の私が使ってみると

はじめに

(対象言語:PHP)

まず私は、オブジェクト指向の信者ではありません。

逆に(逆なのか?)、関数(function)やグローバル変数の多用派です。
オブジェクト指向は便利なら使ってみようか、という程度です。

そんな私がオブジェクト指向を取り入れてみて、便利さを評価しました。
(既存のフレームワークではなく、すべて私の手作りクラスです。)

種類 評価
ログ出力
日時計算
画面操作
セッション
データアクセス(PDO)
データそのもの(RDB) ×

ログ出力 ◎

ログには色んな種類があります。
・デバッグログ
・エラーログ
・アクセスログ

例えばこれらを関数のみで作ると、少し面倒です。
ログ1種類ならともかく、上記のように複数のログを管理するとなると、
配列で状態管理しなければなりません。
それよりは、インスタンスで分けたほうが便利でした。

日時計算 ◎

「日時計算なら元々 DateTimeクラス があるじゃないか」
と言われそうですが、その DateTimeクラスを内包して作りました。
(ただし継承はしていません。)

たとえば日時フォーマット出力(メソッド)で、年は 'Y' だっけ 'y' だっけ?
時間は 'H' だっけ 'h' だっけ? と いちいち迷うんですよ。
なのでその方法は継承せず、よく使うフォーマットをあらかじめ洗い出して、
数値の定数を引数で渡すことでフォーマット出力するメソッドを作ったりしました。

ただし関数と併用しています。
例えばデータベースから取得した日時 (2020-09-20 12:34:56) は文字列なので、
それをいちいちインスタンスにする手間を省くため、文字列を直接 関数に渡して、
内部でインスタンスにして処理する方法も用意しています。これも便利です。

画面操作 ○

画面操作に必要な諸々をカプセル化したものです。

・表示対象データのキー
・次画面の行き先
・戻り画面
・データの最終更新日時
・ワンタイムトークン
・ページネーション情報
などなど

以前はこれらを画面毎にグローバル変数に保持していましたが、
クラス化でだいぶ すっきりしました。

セッション管理 △

作っては見たけど、メリットは感じませんでした。
単に session_start()session_destroy() をするだけだし、
そもそも複数のインスタンスを作る必要もありません。

クラス化せず単なる共通関数でよかったかも。

データアクセス(PDO)○

ほぼPDOですが、PDOは直接 操作せず
それを内包した手作りクラスで、データベースにアクセスするようにしました。

日時計算クラスと同じく、親クラス(PDO)は継承していません。
理由は後述します。

データそのもの(RDB) ×

データアクセスはクラス化できても、
データそのもの(RDB)はクラス化できません。

変数を直接 取得後、変数を直接 参照したほうがよいです。
(システムの種類にもよるのでしょうが。)

私の作っているものは、テーブルが100以上あります。
これらをテーブル毎に いちいちクラス化していたら、
さらにカラムごとにゲッターセッターを作っていたら、いつまで経っても終わりません。
(PDOで連想配列でデータ取得するだけで十分です。)

インターフェイス的なものを作るにしても、共通関数で十分です。

まとめ

日時計算やPDOがそうなのですが、
親クラスの継承はせず、それを隠蔽して、機能を制限して使っています。

というのも、親クラスは往々にして、以下の特徴があるからです。
・全世界の人に使ってもらうため、汎用的に作られている。
・よって細かい(使わない)機能が多くなりがち。

私は必要最低限の範囲で、オブジェクト指向を利用したいと思います。

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

URLについて

"URLとは何か?"

Webサイトは言わば百科事典のように、色んな情報がカテゴライズされており、階層構造 になっています。

「店舗詳細→メニュー一覧→メニュー詳細」
というように詳しい情報を見たい場合は見た い情報を階層的に指定します。

例えば、店舗ID:105にあるメニューID:3000の詳細な情報に対して何らかの操作をしたいとなった場合、URLは以下のようにするとわかりやすいです。

/shops/105/menus/3000
このとき、情報を取得したい場合は、HTTPメソッドとしてGETが使われます。

↑は一例で、他のサイトを見てみると
Retty : https://retty.me/area/PRE13/ARE1/SUB101/100001472413/photos/?kind=3
東京新宿にあるとある店舗の内観の写真一覧を見たい場合は、

 area → 地域指定
 PRE13 → 東京都を指定
 ARE1 → おそらく新宿エリア      
 SUB101 → 新宿東口/歌舞伎町エリア、らしい  
 100001472413 → 店舗ID      
 photos → 写真一覧
 kind=3 → 写真の種類を指定(3が内観らしい)

といった感じになってます。

一番最初にareaとした場合はその後都道府県、大エリア、小エリア、と指定するURL構造 になっていて、店舗IDを指定したあとは、さらなる詳細情報を見る際には photos(写真一覧)だったり、 menu(メニュー/コース)を指定してするようにしているみたいです。

"HTTPメソッドとは?"

例えば、私たちがAmazonでショッピングする際に、まずやることとしては、

「Amazonのショッピングサイトにアクセスすること」ですよね。

このときに、コンピューターの内部ではどんな処理が行われているかというと...
クライアント(Webブラウザ)がサーバーに対して、以下のようなやりとりがあります。

クライアント:「Amazonのショッピングサイトがみたいので、ページの情報をください~」
サーバー:「はい、少々お待ちください」

このとき、ページの情報を取得するためにはGETメソッドが使われます。

他にも、HTTPメソッドにはGET以外にも
・POST = 情報を新規登録をする
・PUT = 情報を更新
・DELETE = 情報を削除があります。

4つのメソッドがありますが、一般的に使われているのは、GETとPOSTだけでいろんな 操作が行われています。

・GET = 情報を取得
・PUT = 情報を更新(新規登録や削除も含む)

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

Laravel + Instagram Basic Display APIで投稿を取得する

はじめに

Laravelを触ってみながら、Instagram Basic Display API というAPIで自分のInstagramアカウントの投稿写真を 取得していこうと思います。

やること

  • Instagram Basic Display APIに必要なアクセストークンを取得する
  • Laravelで投稿を取得&表示する
  • Laravelでアクセストークンを定期更新する

動作環境

  • PHP 7.1.23
  • Laravel 5.8
  • MySQL 5.7

Instagram Basic Display APIとは

Instagram基本表示APIはHTTPベースのAPIです。アプリはこれを使用して、Instagramユーザーのプロフィール、画像、動画、アルバムを取得できます。

InstagramグラフAPIの簡易版で認証したユーザーに関する情報を取得できます。
InstagramグラフAPIと違って審査がいらないのでサクッと使いたい人向けです。

できること

  • ユーザのアクセス許可、アクセストークンの取得
  • 発行したアクセストークンを使用して
    • ユーザーのプロフィールを取得
    • ユーザーの画像、動画、アルバムを取得

基本的に自分のアカウントの投稿をサイトに表示するなどの目的で使うのであればInstagram Basic Display APIで大丈夫だと思います。

前提

スタートガイド

上記の公式のスタートガイドを参考にステップ3までを進めた状態で手順を進めていきます。

  • ステップ1: Facebookアプリを作成する
  • ステップ2: Instagram基本表示を構成する
  • ステップ3: Instagramテストユーザーを追加する

ステップ4のテストユーザーを認証する~長期アクセストークンを取得するまでを書いていきます。

アクセストークンとアクセス許可を取得する

参考 : アクセストークンとアクセス許可を取得する

Instagram Basic Display APIを使うにはアクセストークンが必要になるので取得しましょう。

リクエスト例: ユーザーのプロフィールを取得する

GET /me?fields={fields}&access_token={access-token}

認証を取得する

https://api.instagram.com/oauth/authorize
  ?client_id={instagram-app-id}
  &redirect_uri={redirect-uri}
  &scope=user_profile,user_media
  &response_type=code

instagram-app-id
[アプリダッシュボード] > [製品] > [Instagram] > [基本表示] で表示される InstagramアプリID
スクリーンショット 2020-09-22 1.54.44.png

redirect-uri
[アプリダッシュボード] > [製品] > [Instagram] > [基本表示] で設定した クライアントOAuth設定の 有効なOAuthリダイレクトURL

スクリーンショット 2020-09-22 1.58.03.png

上記の内容を自分の設定に変えてアクセスします。
アクセス例

https://api.instagram.com/oauth/authorize
  ?client_id=990602627938098
  &redirect_uri=https://socialsizzle.herokuapp.com/auth/
  &scope=user_profile,user_media
  &response_type=code

アクセスすると認証許可の画面が出てくるのでテスターとして登録したアカウントで許可しましょう

成功した場合のリダイレクト例

https://socialsizzle.herokuapp.com/auth/?code=AQBx-hBsH3...#_

成功するとredirect_uriに設定したURL + ?code=AQBx-hBsH3...#_

こんな感じの文字列がURLのバーに表示されています。code= と #_ の間の文字列をこのあと使うので控えておきましょう。
この文字列をcodeといいこのあと使います。

短命のアクセストークンを取得する

認証した際に取得した codeを用いて短命のアクセストークンを取得します。

POST https://api.instagram.com/oauth/access_token

リクエストの例

curl -X POST \ 
https://api.instagram.com/oauth/access_token \ 
-F client_id={instagram-app-id} \ 
-F client_secret={instagram-app-secret} \ 
-F grant_type=authorization_code \ 
-F redirect_uri={redirect_uri} \ 
-F code={さっき取得したcode}

instagram-app-secret
[アプリダッシュボード] > [製品] > [Instagram] > [基本表示] で表示される Instagram App Secret

スクリーンショット 2020-09-22 1.54.44.png

成功した時の応答例

{ "access_token": "IGQVJ...", "user_id": 17841405793187218 }

access_token ここの内容がAPIに必要なアクセストークンになります。

しかしこのままだとこのアクセストークンの使用できる期間は1時間と短命なので、長命のものと交換しましょう.

長期のアクセストークンを取得する

参考: 長期アクセストークン

curl -i -X GET "https://graph.instagram.com/access_token?grant_type=ig_exchange_token&client_secret={instagram-app-secret}&access_token={short-lived-access-token}"

short-lived-access-token 先ほど取得した access_token

成功した時の応答例

{ "access_token":"{long-lived-user-access-token}", "token_type": "bearer", "expires_in":5183944 // トークンが有効期限切れになるまでの秒数 }

long-lived-user-access-tokenが今回取得した長期トークンになります。

しかし長期とは言ってもこれも expires_inに記載されている通り期限が存在します。
このトークンに関しては60日間有効なのですがそれをすぎると無効になってしまうため定期的にトークンを更新しないといけません。

長期のアクセストークン更新する

参考: 長期アクセストークンの更新

最長でもアクセストークンは60日しか持たないので有効期限が切れる前に新しいアクセストークンと交換します。

リクエスト例

curl -i -X GET "https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token={long-lived-access-token}"

long-lived-access-token 取得している長期アクセストークン

有効期間が過ぎていない長期アクセストークンを指定してください

応答の例

{ "access_token":"{long-lived-user-access-token}", "token_type": "bearer", "expires_in":5183944 // トークンが有効期限切れになるまでの秒数 }

long-lived-user-access-tokenが新たに取得した60日の有効期限の長期アクセストークンになります。

ユーザーの投稿を取得する

参考: メディアデータを取得する
参考: paging

GET /me/media?fields={fields}&access_token={access-token}

アクセス例

curl -X GET \ 
'https://graph.instagram.com/me/media?fields=id,caption,permalink,media_url,thumbnail_url&access_token={access-token}'

access_token は有効なアクセストークンを設定します。

fieldsで取得するデータを選択できます。

https://developers.facebook.com/docs/instagram-basic-display-api/reference/media#fields

応答例

{
   "data": [
      {
         "id": "メディアID",
         "caption": "キャプションテキスト",
         "permalink": "投稿先URL",
         "media_url": "画像URL"
      }
   ],
  "paging": {
    "cursors": {
      "after": "MTAxN...",
      "before": "NDMyN..."
      },
    "next": "https://graph.faceb..."
  }
}

Laravel を利用する

プロジェクト作成

$ composer create-project --prefer-dist laravel/laravel instagram "5.8.*"
$ cd instagram

コントローラー作成

$ php artisan make:controller InstagramController

ルーティング追加

/routes/web.php
Route::get('/instagram', 'InstagramController@index');

token保存用の簡易的なテーブルを追加します

$ php artisan make:migration create_instagramTokens_table
/database/migrations/...._create_instagram_tokens_table.php
<?php

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

class CreateInstagramTokensTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('instagram_tokens', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('access_token');
            $table->string('token_type');
            $table->integer('expires_in');
            $table->timestamps();
        });
    }

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

$ php artisan migrate
mysql> desc instagram_tokens
    -> ;
+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| id           | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| access_token | varchar(255)        | NO   |     | NULL    |                |
| token_type   | varchar(255)        | NO   |     | NULL    |                |
| expires_in   | int(11)             | NO   |     | NULL    |                |
| created_at   | timestamp           | YES  |     | NULL    |                |
| updated_at   | timestamp           | YES  |     | NULL    |                |
+--------------+---------------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)

モデル作成

$ php artisan make:model InstagramToken

今回は最初の手順で有効な長期access_tokenを取得しているのでenvに追加しておきます。

/.env
INSTAGRAM_TOKEN=取得した有効な長期アクセストークン

Laravelでアクセストークンを更新

参考: Laravelのタスクスケジュール(cron)を使いこなす

Commandの作成

$ php artisan make:command Instagram
/app/Console/Commands/Instagram.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\InstagramToken;

class Instagram extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'instagram:refresh';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Instagram access token update';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        // ここに処理を書いていく
    }
}


Commandの登録

/app/Console/Kernel.php
    protected $commands = [
      Commands\Instagram::Class,
    ];

トークンを更新する

/app/Console/Commands/Instagram.php
/**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
      $baseUrl = "https://graph.instagram.com/refresh_access_token?";

      // アクセストークン取得
      $instagramToken = InstagramToken::select('access_token')->latest()->first();
      $accessToken = env('INSTAGRAM_TOKEN');

      if ($instagramToken->access_token) {
        $accessToken = $instagramToken->access_token;
      }

      // パラメーター設定
      $params = array(
        'grant_type' => 'ig_refresh_token',
        'access_token' => $accessToken
      );

      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $baseUrl . http_build_query($params));
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

      $responseText = curl_exec($ch);
      $result = json_decode($responseText, true);

      curl_close($ch);

      // DB保存
      if ($newAccessToken = $result['access_token']) {
        $instagramToken = new InstagramToken();
        $instagramToken->access_token = $newAccessToken;
        $instagramToken->token_type = $result['token_type'];
        $instagramToken->expires_in = $result['expires_in'];
        $instagramToken->save();
      }

    }

実行してみる

$ php artisan instagram:refresh
mysql> select * from instagram_tokens;
+----+-------------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+---------------------+---------------------+
| id | access_token                                                                                                                                    | token_type | expires_in | created_at          | updated_at          |
+----+-------------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+---------------------+---------------------+
|  1 | トークン | bearer     |    5177185 | 2020-09-21 21:53:07 | 2020-09-21 21:53:07 |
|  2 | トークン  | bearer     |    5177002 | 2020-09-21 21:56:10 | 2020-09-21 21:56:10 |
|  3 | トークン | bearer     |    5176998 | 2020-09-21 21:56:14 | 2020-09-21 21:56:14 |
+----+-------------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+---------------------+---------------------+
3 rows in set (0.00 sec)

取得できていますね。
これを定期実行すれば有効期限問題は解消できそうです。

参考: Laravelのタスクスケジュール(cron)を使いこなす
などを参考に有効期限の60日が来る前に定期的にトークンを更新するように設定しましょう

scheduleの追加&cron設定

今回はscheduleに追加する形で実装していこうと思います。
scheduleではなくそのままphp artisan instagram:refreshこのコマンドをcron設定してもいいと思います。
scheduleにコマンドを追加することでcron設定を一個に済ませることができます
今回は実行するコマンドが一個なのであんまり恩恵はないかもしれませんが練習も兼ねてやろうと思います。

# エディタが開く
$ crontab -e

# * * * * *  左から「分」「時」「日」「月」「曜日」
# 分 0-59
# 時 0-23
# 日 1-31
# 月 1-12
# 曜日 0-7 (0または7は日曜日)
# * * * * * で毎分

# 下記の行を開いたエディタに追加
* * * * * cd [プロジェクトのパス] && php artisan schedule:run >> /dev/null 2>&1

スケジュールへの登録

参考: Laravel タスクスケジュールまとめ

/app/Console/Kernel.php
    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
      // 5分ごとに実行
      $schedule->command('instagram:refresh')
                ->everyFiveMinutes();
    }
mysql> select * from instagram_tokens;
+----+--------------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+---------------------+---------------------+
| id | access_token                                                                                                                                     | token_type | expires_in | created_at          | updated_at          |
+----+--------------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+---------------------+---------------------+
|  8 |トークン   | bearer     |    5156672 | 2020-09-22 03:35:01 | 2020-09-22 03:35:01 |
|  9 |トークン  | bearer     |    5156371 | 2020-09-22 03:40:01 | 2020-09-22 03:40:01 |
+----+--------------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+---------------------+---------------------+

ちゃんと設定通りに定期実行されていますね

簡単な表示画面を作成します

/app/Http/Controllers/InstagramController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class InstagramController extends Controller
{
  public function index ()
  {
    return view('instagram');
  }
}
/resources/views/instagram.blade.php
<html>
  <head>
    <title>HelloWorld</title>
  </head>
  <body>
    <h1>HelloWorld!</h1>
  </body>
</html>

サーバー起動

$ php artisan serve

http://127.0.0.1:8000/instagram
アクセスできればOK

投稿した画像を取得

/app/Http/Controllers/InstagramController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\InstagramToken;

class InstagramController extends Controller
{
  public function index ()
  {
    $baseUrl = "https://graph.instagram.com/me/media?";

    // アクセストークン取得
    $instagramToken = InstagramToken::select('access_token')->latest()->first();
    $accessToken = env('INSTAGRAM_TOKEN');

    if ($instagramToken->access_token) {
      $accessToken = $instagramToken->access_token;
    }

    // パラメーター設定
    $params = array(
      'fields' => implode(',', array('id','caption','permalink','media_url','thumbnail_url')),
      'access_token' => $accessToken
    );

    //curlセッション初期化
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $baseUrl . http_build_query($params));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $responseText = curl_exec($ch);
    $result = json_decode($responseText, true);

    //セッション終了
    curl_close($ch);

    return view('instagram', [
      'mediaData' => $result['data'],
      'paging' => $result['paging']
    ]);
  }
}

投稿した画像を表示

/resources/views/instagram.blade.php
<html>
  <head>
    <title>HelloWorld</title>
  </head>
  <body>
    @foreach ($mediaData as $media)
      <a href="{{$media['permalink']}}" target="_brank" rel="noopener">
        <img src="{{$media['media_url']}}" width="100px" alt="{{$media['caption']}}">
      </a>
    @endforeach
  </body>
</html>

結果

スクリーンショット 2020-09-22 5.32.36.png

最後に

実装方法について
Laravelでcommandを作成し、定期実行すると言うのは初めてだったので結構楽しかったです。アクセストークンは定期実行、投稿の取得は毎回APIを実行という方法を今回取っていたのですが、
APIリクエストの節約の為に1日1回夜中に投稿を取得するようにし,その結果をjsonファイルとして出力,またはDBに保存するなどすればリクエスト数を節約出来ると思いました。
もっとこう言う良い方法ある等あれば教えてもらえると嬉しいです。

Instagram Basic Display APIについて
自分のブログなどにインスタで投稿したものを表示させたいなら、Instagram Basic Display APIで十分そうですね。今回は「InstagramのAPI使いたい!」と思ってやったのですが,よくよく考えたら僕自身が映える写真を投稿しているアカウントを持っていなかったので,今回のAPIだといい感じの写真が表示されませんでした。。。

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

Laravel CRUD処理を使った投稿アプリを作成する その9 バリデーションをRequestに記載する

目的

  • アプリを作成する上で基本となるCRUD処理を有したLaravelアプリをチュートリアル的に作成する方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提条件

前提情報

  • ソースコードはこちら→https://github.com/miriwo0104/laravel_crud/tree/master
  • DockerやAWSなどは使用せずにMacのローカルに実施環境と同じLaravel開発環境を構築して実施する。
  • チュートリアルで実際に筆者が作成したソースコードをGitHubにて公開予定である。
  • CRUD処理の作成完了を最短目標にしてバリデーションなどは後々設定することとする。
  • 実施環境と同じ環境がDockerやAWSで用意できるなら都度読み替えていただければ実施が可能だと思う。
  • 公式ドキュメントと一冊の技術書を元に本記事を記載する。
  • DBへの意図しないデータの流入を防ぐためバリデーションを実装する。
  • 問い合わせの内容は「入力がされていること」のバリデーションルールを、新規投稿と投稿編集は「入力されており、かつ140文字以内であること」定義してバリデートする。
  • バリデーションで弾かれた時にエラーが出力される様にする。
  • 下記に目を通しておくと理解が早いかもしれない。
  • バリデーションルールは下記の公式ドキュメントに数多く記載されているので実装できたら遊んでみるのもいいかもしれない。

この記事の読後感

  • ユーザが入力する部分にバリデーションを実装することができ、バリデーションルールをRequestファイルに記載する。

概要

  1. Requestファイルの作成と記載
  2. コントローラファイルの記載修正
  3. 確認

詳細

  1. Requestファイルの作成と記載

    1. laravel_crudディレクトリで下記コマンドを実行し専用のリクエストファイルを作成する。

      $ php artisan make:request ContentRequest
      
    2. laravel_crudディレクトリで下記コマンドを実行し先に作成したリクエストファイルを開く。

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

      laravel_crud/app/Http/Requests/ContentRequest.php
      <?php
      
      namespace App\Http\Requests;
      
      use Illuminate\Foundation\Http\FormRequest;
      
      class ContentRequest 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 [
                  // 下記を追記する
                  'content' => ['required', 'max: 140'],
              ];
          }
      }
      
    4. laravel_crudディレクトリで下記コマンドを実行し専用のリクエストファイルを作成する。

      $ php artisan make:request InquiryRequest
      
    5. laravel_crudディレクトリで下記コマンドを実行し先に作成したリクエストファイルを開く。

      $ vi app/Http/Requests/InquiryRequest.php
      
    6. 先に開いたリクエストファイルを下記の様に修正する。

      laravel_crud/app/Http/Requests/InquiryRequest.php
      <?php
      
      namespace App\Http\Requests;
      
      use Illuminate\Foundation\Http\FormRequest;
      
      class InquiryRequest 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 [
                  // 下記を追記する
                  'content' => ['required'],
                  'name' => ['required'],
                  // 上記までを追記する
              ];
          }
      }
      
  2. コントローラファイルの記載修正

    1. laravel_crudディレクトリで下記コマンドを実行しコントローラファイルを開く。

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

      laravel_crud/app/Http/Controllers/ContentController.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      use App\Models\Content;
      // 下記を追記する
      use App\Http\Requests\ContentRequest;
      
      class ContentController extends Controller
      {
          public function input()
          {
              return view('contents.input');
          }
      
          // 下記を修正する
          public function save(ContentRequest $contentRequest)
          {
              // バリデーションルールとバリデート命令を削除する
              $input_content = new Content();
              // 下記を修正する
              $input_content->content = $contentRequest['content'];
              $input_content->save();
      
              return redirect('/output');
          }
      
          public function output()
          {
              $contents_get_query = Content::select('*');
              $all_contents = $contents_get_query->get();
      
              return view('contents.output', [
                  'all_contents' => $all_contents,
              ]);
          }
      
          public function delete(Request $request)
          {
              $contents_delete_query = Content::select('*');
              $contents_delete_query->where('id', $request['content_id']);
              $contents_delete_query->delete();
      
              return redirect('/output');
          }
      
          public function edit($content_id)
          {
              $contents_edit_query = Content::select('*');
              $contents_edit_query->where('id', $content_id);
              $edit_contents = $contents_edit_query->get();
              $edit_content = $edit_contents[0];
      
              return view('contents.edit', [
                  'edit_content' => $edit_content,
              ]);
          }
      
          // 下記を修正する
          public function update(ContentRequest $contentRequest)
          {
              // バリデーションルールとバリデート命令を削除する
              $contents_update_query = Content::select('*');
              // 下記を修正する
              $contents_update_query->where('id', $contentRequest['content_id']);
              $update_contents = $contents_update_query->get();
      
              $update_content = $update_contents[0];
              // 下記を修正する
              $update_content->content = $contentRequest['content'];
              $update_content->save();
      
              return redirect('/output');
          }
      }
      
    3. laravel_crudディレクトリで下記コマンドを実行しコントローラファイルを開く。

      $ vi app/Http/Controllers/InquiryController.php
      
    4. 先に開いたファイルを下記の様に修正する。

      laravel_crud/app/Http/Controllers/InquiryController.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      use Illuminate\Support\Facades\Mail;
      use Illuminate\Support\Facades\Auth;
      use App\Mail\SendMail;
      // 下記を追記する
      use App\Http\Requests\InquiryRequest;
      
      class InquiryController extends Controller
      {
          public function input()
          {
              $user_infos = Auth::user();
              return view('inquiries.input', [
                  'user_infos' => $user_infos,
              ]);
          }
      
          // 下記を修正する
          public function send(InquiryRequest $inquiryRequest)
          {
              // バリデーションルールとバリデート命令を削除する
              // 下記を修正する
              $inquiry_content = $inquiryRequest->all();
              Mail::to('admin@example')->send(new SendMail($inquiry_content));
              return redirect(route('home'));
          }
      }
      
  3. 確認

    1. laravel_crudディレクトリで下記コマンドを実行しローカルサーバを起動する。

      $ php artisan serve
      
    2. 下記にユーザ認証後、下記にアクセスする。

    3. 何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。

      127_0_0_1_8000_input.png

    4. 下記にユーザ認証後、下記にアクセスする。(コンテンツのidは1以外でも構わない)

    5. 何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。

      127_0_0_1_8000_edit_5.png

    6. 下記にユーザ認証後、下記にアクセスする。(コンテンツのidは1以外でも構わない)

    7. 何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。

      127_0_0_1_8000_inquiry_input.png

参考文献

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

Laravel CRUD処理を使った投稿アプリを作成する その8 バリデーションを実装する

目的

  • アプリを作成する上で基本となるCRUD処理を有したLaravelアプリをチュートリアル的に作成する方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提条件

前提情報

  • ソースコードはこちら→https://github.com/miriwo0104/laravel_crud/tree/master
  • DockerやAWSなどは使用せずにMacのローカルに実施環境と同じLaravel開発環境を構築して実施する。
  • チュートリアルで実際に筆者が作成したソースコードをGitHubにて公開予定である。
  • CRUD処理の作成完了を最短目標にしてバリデーションなどは後々設定することとする。
  • 実施環境と同じ環境がDockerやAWSで用意できるなら都度読み替えていただければ実施が可能だと思う。
  • 公式ドキュメントと一冊の技術書を元に本記事を記載する。
  • DBへの意図しないデータの流入を防ぐためバリデーションを実装する。
  • 問い合わせの内容は「入力がされていること」のバリデーションルールを、新規投稿と投稿編集は「入力されており、かつ140文字以内であること」定義してバリデートする。
  • バリデーションで弾かれた時にエラーが出力される様にする。
  • 下記に目を通しておくと理解が早いかもしれない。
  • バリデーションルールは下記の公式ドキュメントに数多く記載されているので実装できたら遊んでみるのもいいかもしれない。

この記事の読後感

  • ユーザが入力する部分にバリデーションを実装することができる。

概要

  1. バリデーションの実装
  2. エラー出力部分とデフォルト値の設定
  3. 確認

詳細

  1. バリデーションの実装

    1. laravel_crudディレクトリで下記コマンドを実行してコントローラファイルを開く。

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

      laravel_crud/app/Http/Controllers/ContentController.php
      public function save(Request $request)
      {
          // 下記を追記する
          $rules = [
              'content' => ['required', 'max: 140'],
          ];
      
          $this->validate($request, $rules);
          // 上記までを追記する
          $input_content = new Content();
          $input_content->content = $request['content'];
          $input_content->save();
      
          return redirect('/output');
      }
      
    3. 先に開いたコントローラファイルのupdateアクションを下記の様に修正する。

      laravel_crud/app/Http/Controllers/ContentController.php
      public function update(Request $request)
      {
          // 下記を追記する
          $rules = [
              'content' => ['required', 'max: 140'],
          ];
      
          $this->validate($request, $rules);
          // 上記までを追記する
          $contents_update_query = Content::select('*');
          $contents_update_query->where('id', $request['content_id']);
          $update_contents = $contents_update_query->get();
      
          $update_content = $update_contents[0];
          $update_content->content = $request['content'];
          $update_content->save();
      
          return redirect('/output');
      }
      
    4. ContentController.phpの全体像を下記に記載する。

      laravel_crud/app/Http/Controllers/ContentController.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      use App\Models\Content;
      
      class ContentController extends Controller
      {
          public function input()
          {
              return view('contents.input');
          }
      
          public function save(Request $request)
          {
              // 下記を追記する
              $rules = [
                  'content' => ['required', 'max: 140'],
              ];
      
              $this->validate($request, $rules);
              // 上記までを追記する
              $input_content = new Content();
              $input_content->content = $request['content'];
              $input_content->save();
      
              return redirect('/output');
          }
      
          public function output()
          {
              $contents_get_query = Content::select('*');
              $all_contents = $contents_get_query->get();
      
              return view('contents.output', [
                  'all_contents' => $all_contents,
              ]);
          }
      
          public function delete(Request $request)
          {
              $contents_delete_query = Content::select('*');
              $contents_delete_query->where('id', $request['content_id']);
              $contents_delete_query->delete();
      
              return redirect('/output');
          }
      
          public function edit($content_id)
          {
              $contents_edit_query = Content::select('*');
              $contents_edit_query->where('id', $content_id);
              $edit_contents = $contents_edit_query->get();
              $edit_content = $edit_contents[0];
      
              return view('contents.edit', [
                  'edit_content' => $edit_content,
              ]);
          }
      
          public function update(Request $request)
          {
              // 下記を追記する
              $rules = [
                  'content' => ['required', 'max: 140'],
              ];
      
              $this->validate($request, $rules);
              // 上記までを追記する
              $contents_update_query = Content::select('*');
              $contents_update_query->where('id', $request['content_id']);
              $update_contents = $contents_update_query->get();
      
              $update_content = $update_contents[0];
              $update_content->content = $request['content'];
              $update_content->save();
      
              return redirect('/output');
          }
      }
      
      
    5. laravel_crudディレクトリで下記コマンドを実行してコントローラファイルを開く。

      $ vi app/Http/Controllers/InquiryController.php
      
    6. 先に開いたコントローラファイルのsendアクションを下記の様に修正する。

      laravel_crud/app/Http/Controllers/InquiryController.php
      public function send(Request $request)
      {
          // 下記を追記する
          $rules = [
              'content' => ['required'],
              'name' => ['required'],
          ];
      
          $this->validate($request, $rules);
          // 上記までを追記する
          $inquiry_content = $request->all();
          Mail::to('admin@example')->send(new SendMail($inquiry_content));
          return redirect(route('home'));
      }
      
    7. InquiryController.phpの全体像を下記に記載する。

      laravel_crud/app/Http/Controllers/InquiryController.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      use Illuminate\Support\Facades\Mail;
      use Illuminate\Support\Facades\Auth;
      use App\Mail\SendMail;
      
      class InquiryController extends Controller
      {
          public function input()
          {
              $user_infos = Auth::user();
              return view('inquiries.input', [
                  'user_infos' => $user_infos,
              ]);
          }
      
          public function send(Request $request)
          {
              // 下記を追記する
              $rules = [
                  'content' => ['required'],
                  'name' => ['required'],
              ];
      
              $this->validate($request, $rules);
              // 上記までを追記する
              $inquiry_content = $request->all();
              Mail::to('admin@example')->send(new SendMail($inquiry_content));
              return redirect(route('home'));
          }
      }
      
    8. laravel_crudディレクトリで下記コマンドを実行しローカルサーバを起動する。

      $ php artisan serve
      
    9. 下記にユーザ認証後、下記にそれぞれアクセスし内容を入力せずに送信ボタンをクリックし、ページ遷移せず同じページが再度読み込まれることを確認する。編集画面での確認はidが1以外のものでも良い。

  2. エラー出力部分とデフォルト値の設定

    1. laravel_crudディレクトリで下記コマンドを実行して新規投稿画面のビューファイルを開く。

      $ vi resources/views/contents/input.blade.php
      
    2. 開いたファイルを下記の様に修正する。

      laravel_crud/resources/views/contents/input.blade.php
      <h1>input</h1>
      {{-- 下記を追記する --}}
      @error('content')
          {{ $message }}
      @enderror
      {{-- 上記までを追記する --}}
      <form action="{{route('save')}}" method="post">
          @csrf
          {{-- 下記を修正する --}}
          <textarea name="content" cols="30" rows="10">{{ old('content') }}</textarea>
          <input type="submit" value="送信">
      </form>
      
    3. laravel_crudディレクトリで下記コマンドを実行して投稿編集画面のビューファイルを開く。

      $ vi resources/views/contents/edit.blade.php
      
    4. 開いたファイルを下記の様に修正する。

      laravel_crud/resources/views/contents/edit.blade.php
      <h1>edit</h1>
      {{-- 下記を追記する --}}
      @error('content')
          {{ $message }}
      @enderror
      {{-- 上記までを追記する --}}
      <form action="{{route('update')}}" method="post">
          @csrf
          <textarea name="content" cols="30" rows="10">{{$edit_content['content']}}</textarea>
          <input type="hidden" name="content_id" value="{{$edit_content['id']}}">
          <input type="submit" value="送信">
      </form>
      
    5. laravel_crudディレクトリで下記コマンドを実行して投稿編集画面のビューファイルを開く。

      $ vi resources/views/contents/edit.blade.php
      
    6. 開いたファイルを下記の様に修正する。

      laravel_crud/resources/views/contents/edit.blade.php
      <h1>edit</h1>
      {{-- 下記を追記する --}}
      @error('content')
          {{ $message }}
      @enderror
      {{-- 上記までを追記する --}}
      <form action="{{route('update')}}" method="post">
          @csrf
          <textarea name="content" cols="30" rows="10">{{$edit_content['content']}}</textarea>
          <input type="hidden" name="content_id" value="{{$edit_content['id']}}">
          <input type="submit" value="送信">
      </form>
      
    7. laravel_crudディレクトリで下記コマンドを実行して問い合わせ送信画面のビューファイルを開く。

      $ vi resources/views/inquiries/input.blade.php
      
    8. 開いたファイルを下記の様に修正する。

      laravel_crud/resources/views/inquiries/input.blade.php
      <form action="{{ route('inquiry.send') }}" method="POST">
          @csrf
          <p>お問い合わせ内容</p>
          <textarea name="content" cols="30" rows="10"></textarea>
          {{-- 下記を追記する --}}
          @error('content')
          {{ $message }}
          @enderror
          {{-- 上記までを追記する --}}
      
          <p>お客様のお名前</p>
          <input type="text" name="name" value="{{ $user_infos['name'] }}">
          {{-- 下記を追記する --}}
              @error('name')
              {{ $message }}
          @enderror
          {{-- 上記までを追記する --}}
      
          <p>お客様のメールアドレス</p>
          <p>{{ $user_infos['email'] }}</p>
          <input type="hidden" name="email" value="{{ $user_infos['email'] }}">
          <br>
          <input type="submit" value="送信">
      </form>
      
  3. 確認

    1. laravel_crudディレクトリで下記コマンドを実行しローカルサーバを起動する。

      $ php artisan serve
      
    2. 下記にユーザ認証後、下記にアクセスする。

    3. 何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。

      127_0_0_1_8000_input.png

    4. 下記にユーザ認証後、下記にアクセスする。(コンテンツのidは1以外でも構わない)

    5. 何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。

      127_0_0_1_8000_edit_5.png

    6. 下記にユーザ認証後、下記にアクセスする。(コンテンツのidは1以外でも構わない)

    7. 何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。

      127_0_0_1_8000_inquiry_input.png

参考文献

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

冴えないAWS環境の育てかた α を育ててみた�<準備編>

はじめに

クラスメソッドさんの技術ブログ「Developers.io」にてこの様な記事が投稿されました
https://dev.classmethod.jp/articles/saenai-aws-1/

要約すると
Saenai_001.png

この様な稀によく見るAWS環境を「Well Architected Framework」を意識しつつ、

  • AWSアカウントの育てかた
  • ネットワークの育てかた
  • サーバーの育てかた
  • データストアの育てかた

の4つに分けて育てていくという記事です。

ちなみに最終的にこの様なアーキテクチャに育っていきます
Saenai_104-960x1011.png

今日から約1〜2ヶ月間、力試しという形で自分なりに+αでできることも加えつつ、このアーキテクチャに挑戦してみます!

準備編

今日は準備編ということで、元となるアーキテクチャを設計していこうと思います。

スクリーンショット 2020-09-20 23.09.18.png

デプロイ対象のアプリケーションはフレームワークの練習用に作成した。Laravelの掲示板アプリを使用しました。

ネットワーク作成

まずはCloudfomationを利用して、VPC、パブリックサブネット、プライベートサブネットを作成。

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'false'
  eip:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
  subnetPub1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/24
      AvailabilityZone:
        Fn::Select:
        - '0'
        - Fn::GetAZs:
            Ref: AWS::Region
      VpcId: !Ref VPC
  subnetPub2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.1.0/24
      AvailabilityZone:
        Fn::Select:
        - '1'
        - Fn::GetAZs: !Ref AWS::Region
      VpcId: !Ref VPC
  subnetPrv1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.2.0/24
      AvailabilityZone:
        Fn::Select:
        - '0'
        - Fn::GetAZs: !Ref AWS::Region
      VpcId: !Ref VPC
  subnetPrv2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.3.0/24
      AvailabilityZone:
        Fn::Select:
        - '1'
        - Fn::GetAZs: !Ref AWS::Region
      VpcId: !Ref VPC
  Nat:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
        - eip
        - AllocationId
      SubnetId: !Ref subnetPub1
    DependsOn: eip
  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
  RouteTablePublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  RouteTablePrivate:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  gw:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref IGW
  subnetRoutePub1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref subnetPub1
  subnetRoutePub2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref subnetPub2
  subnetRoutePrv1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePrivate
      SubnetId: !Ref subnetPrv1
  subnetRoutePrv2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePrivate
      SubnetId: !Ref subnetPrv2
  routePublic:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePublic
      GatewayId: !Ref IGW
    DependsOn: gw
  routePrivate:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePrivate
      NatGatewayId: !Ref Nat
    DependsOn:
    - Nat
    - subnetRoutePrv1
    - subnetRoutePrv2

完全なinfrastructure as codeに憧れを持ってますが、コード管理しちゃうと手作業の差分とかの管理が難しそうで、まだ手をつけられてないです...

サーバー

適当にEC2サーバーを立てちゃいます。
インスタンスタイプはとりあえず無料枠の「t2.micro」

とりあえずSSH接続

#とりあえず更新
$ sudo yum update -y

#Apach,php,Mysqlのインストール
$ sudo amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
$ sudo yum install -y httpd mariadb-server

#Apache ウェブサーバーを起動
$ sudo systemctl start httpd

#システムがブートするたびに Apache ウェブサーバーが起動するように設定
$ sudo systemctl enable httpd

composerのインストール

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

# パスを通す
$ sudo mv composer.phar /usr/local/bin/composer

gitからアプリを持ってくる

$ sudo yum install git
$ cd /var/www
$ git clone https://github.com/mkoki0422/Laravel_keiziban.git

Apacheの設定

DocumentRootを変更

$ sudo nano /etc/httpd/conf/httpd.conf

DocumentRoot "/var/www/html"
→
DocumentRoot "/var/www/Laravel-keiziban/public"
に変更

.htaccessの有効化

#http.confファイルに追加
<Directory /var/www/Laravel-keiziban/public>
 AllowOverride All
</Directory>

再起動して、設定を適用

$ sudo systemctl restart httpd

vendorディレクトリの作成

$ composer install

.envファイルの作成

#.envファイルの作成
$ cp .env.example .env

# .envのAPP_KEYの生成
$ php artisan key:generate

RDS

サブネットグループ作成

RDSを構成する前にRDSを配置するサブネットグループを作成しましょう。

マネジメントコンソールの「サブネットグループ」から「DBサブネットグループの作成」に進みましょう。
注意 RDSはMultiAZ構成をしない場合でも、2つ以上のAZを指定しないといけません!

スクリーンショット 2020-09-20 23.29.27.png

データベース用セキュリティグループ

DBサーバーに適用するためのセキュリテイグループも事前に作成しておきます。

インバウンドルールのタイプを「MYSQL/Aurora」、ソースはEC2で適用しているセキュリティグループを選択する方法と、IPアドレスを選択する方法がありますが、今回はセキュリティグループをソースとして指定します。

スクリーンショット 2020-09-20 23.34.58.png

DB構築

実際にDBを構築していきます!

エンジンは「MySQL」
テンプレートは「開発/テスト」もしくは「無料利用枠」のどちらかを選択します。
後々MultiAZ構成にするかもしれませんので、今回は「開発/テスト」を選択しました。

注意なるべくコストを抑えたいけど、「開発/テスト」を選択したい場合はDBインスタンスサイズをバースト可能なクラスである「db.t2.micro」を選択しましょう。
デフォルトだと月額345.86 USDかかってしまいます....

「接続」からVPCの指定
「追加の接続設定」からサブネットグループの指定、セキュリティグループの指定、マスターDBを設置するAZの指定をしましょう。
追加設定から、実際に使用するDB名も指定しておきます。

項目 設定値
エンジン MySQL
テンプレート 開発/テスト OR 無料利用枠
接続 今回作成したVPC
セキュリティグループ 先ほど作成したSG(Defaulは削除して下さい)
サブネットグループ 先ほど作成したサブネットグループ
AZ マスターDBを配置したいAZを指定
DB名 実際に利用するDB名
マスタユーザー マスターユーザー名とパスワード

LaravelのDB接続設定

DBサーバーの構築が完了しましたので、Laravelの設定を変更してRDSに接続していきます。

.envの設定

webサーバーにSSHログイン後、.envファイルを変更していきます。

cd /var/www/Laravel-keiziban
sudo nano .env

.envファイルに四項目を設定して下さい。

項目 設定値
DB_HOST RDSのエンドポイント
DB_DATABASE RDSを構築した際に指定した、DB名
DB_USERNAME RDSを構築した際に指定した、マスターユーザー名
DB_PASSWORD RDSを構築した際に指定した、マスターパスワード

migrateの実行

php artisan migrate

ここでエラーがでなければ接続完了です!

実際にアプリを触ってみて、確認してみましょう。

Route53

一旦ゴールデンイメージ(AMI)を作成しまして、Webサーバーを複製します。

スクリーンショット 2020-09-21 0.38.03.png

使ってなかったドメインがあったので、複数値ルーティングで2つのWEBサーバーをルーティングさせました。

明日の予定

今日は準備編ですので、とりあえずデプロイして、RDSに接続、Route53でルーティングさせるところまでやってみました。
明日は

  • Route53 / ヘルスチェックの有効化
  • CloudFront / ディストリビューションの作成
  • CloudFront / 追加のメトリックを有効化
  • CloudFront / アクセスログの取得
  • CloudFront / 特定地域からのアクセスをブロック
  • CloudFront / カスタムエラーレスポンスとしてSorry Pageを構成
  • CloudFront / 監視
  • ACM / サーバー証明書の作成
  • ACM / CloudFrontへの関連付けと適切なセキュリティポリシーの選択

を進めていこうかと思いますー!

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