- 投稿日:2020-09-21T20:31:25+09:00
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確認できます。
なので私の場合は
/Applications/MAMP/bin/php7.x.x/conf/php.ini
ファイルを修正すれば良いということがわかります。その教科書もディレクトリの場所をそのまま載せるのではなく、このようにどうやって設定ファイルの格納場所を調べればいいか教えてくれた方が良かったかもしれませんね。(って偉そうにすみません...)
もしご指摘あれば遠慮なくお願いします。
- 投稿日:2020-09-21T20:31:25+09:00
PHP7の設定ファイルの保管場所の探し方
PHPの設定を変更したかったのですが、とあるPHPの教科書(PHP7対応版)の書いてある通りやってもできずに時間をロスしてしまいました。今度そんなことがにないようにメモをしておきます!
その教科書には、
アプリケーション→MAMP→conf→PHP7.x.x→php.ini
を変更せよと書いてありますが、これは必ずしも正しくありません。少なくとも私の場合は上手くいきませんでした。
自分が使っているPHPがどのphp.iniを読み込んでいるかは、phpファイルの中に下記コードを書き込んでブラウザで読み込むと、インフォメーションが開いて確認できます。index.php<?php phpinfo(); exit();//←こちらはお好みで ?>開いたら、下記スクショのようにLoaded Configuration File確認できます。
なので私の場合は
/Applications/MAMP/bin/php7.x.x/conf/php.ini
ファイルを修正すれば良いということがわかります。その教科書もディレクトリの場所をそのまま載せるのではなく、このようにどうやって設定ファイルの格納場所を調べればいいか教えてくれた方が良かったかもしれませんね。(って偉そうにすみません...)
もしご指摘あれば遠慮なくお願いします。
- 投稿日:2020-09-21T19:58:31+09:00
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.phpfloor((基準とする日付 - 生年月日) / 10000) //(int)/intval()でも可これは日付を数値に変更する必要がある
たとえばexplode()
関数を使うとかimplode()
関数を使うとか、この二つを使えば大体なんとかなる
気が向いたら他の記事で解説したい笑
タイムスタンプから計算する場合は$birthday = (int)date('Ymd', $timestamp)
みたいな感じかな日付が存在するか確認する
example.phpcheckdate(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/1970intval()関数の注意
注意整数型への変換が失敗した際に0や1を返してしまうことがある 全て文字列だった場合、 0が返される 配列だった場合には要素が存在する場合には1、空配列の場合には0が返される 真偽値だった場合はtrue=1, false=0が返される もちろんfloat型の場合は切り捨てられる 数値型だった場合のみ変換したいときはis_numericで判定を行うといい
- 投稿日:2020-09-21T18:45:48+09:00
[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
と打つだけ表示結果
こんばんは
- 投稿日:2020-09-21T16:19:03+09:00
簡易LISP処理系の実装例(PHP版)
【他言語版へのリンク記事】簡易LISP処理系の実装例【各言語版まとめ】
この記事は,下記拙作記事のPHP版を抜粋・修正したものを利用した,簡易LISP処理系("McCarthy's Original Lisp")の実装例をまとめたものです.
『括弧文字列』簡易パーサ実装例まとめ(PHP版はS式入出力を先行作成しました)- リスト処理関数(cons,car,cdr,eq,atom)実装例まとめ
最低限の機能をもった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
の他,cond
とlambda
が使用可能- 組込関数:
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_read
→s_eval
→s_string
をまとめたs_rep
を定義.備考
更新履歴
- 2020-09-21:初版公開
- 投稿日:2020-09-21T15:35:08+09:00
【PHP】投稿種類別で投稿を出力してくれる機能
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>'; $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
- 投稿日:2020-09-21T13:48:10+09:00
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通った!
- 投稿日:2020-09-21T13:40:32+09:00
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 fromvagrant 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 upHomesteadを使ってlaravelの環境構築していたのですが、 Homesteadに移動していなかったので起動できなかったです。。。
- 投稿日:2020-09-21T13:27:25+09:00
画像アップロード掲示板 エラー処理
エラー処理の実装方法
$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に移動
・画像ファイルのアクセス権限を指定エラー処理の書く場所に注意したい。
私は、エラー処理は正しく書けていたが、プログラムの書く場所を間違っていたため、正
しく反映されなかった。
プログラムは上から下に順番に実行されていくことをよく頭に入れておく。
- 投稿日:2020-09-21T13:23:06+09:00
git add コマンドの使い方
$ git add .このコマンドを打つと、コミット前に自分が修正したファイルを一時的に保存しておくことができます。
addの後ろにある「.」を付けることで、例えば修正したファイルが3つあれば、 全て保存することができる。
5つのファイルを修正して、そのうち2つだけコミットしたいときなどは、
「git add ファイル名」にすれば、指定したファイルのみ保存することができる。
- 投稿日:2020-09-21T11:39:17+09:00
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」でマイグレーションが実行され、 テーブルが作成されます。
- 投稿日:2020-09-21T11:34:27+09:00
ちょいと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"); ?>
- 投稿日:2020-09-21T11:28:49+09:00
404エラーが出る原因とは
404とは、”存在しないページを表示させようとしている状態” のことです。
原因は2つあります。
・ URLに指定している変数が渡されてないこと
・ 表示しようとしてるページのURL自体が定義されてないことこれらを確認することで、どこでミスってるのかを探せるといいです。
- 投稿日:2020-09-21T11:27:48+09:00
【オブジェクト指向】懐疑派の私が使ってみると
はじめに
(対象言語: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がそうなのですが、
親クラスの継承はせず、それを隠蔽して、機能を制限して使っています。というのも、親クラスは往々にして、以下の特徴があるからです。
・全世界の人に使ってもらうため、汎用的に作られている。
・よって細かい(使わない)機能が多くなりがち。私は必要最低限の範囲で、オブジェクト指向を利用したいと思います。
- 投稿日:2020-09-21T11:23:41+09:00
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 = 情報を更新(新規登録や削除も含む)
- 投稿日:2020-09-21T03:11:50+09:00
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
redirect-uri
[アプリダッシュボード] > [製品] > [Instagram] > [基本表示] で設定した クライアントOAuth設定の 有効なOAuthリダイレクトURL上記の内容を自分の設定に変えてアクセスします。
アクセス例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成功した時の応答例
{ "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日の有効期限の長期アクセストークンになります。ユーザーの投稿を取得する
参考: メディアデータを取得する
参考: pagingGET /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.phpRoute::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に追加しておきます。
/.envINSTAGRAM_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.phpprotected $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スケジュールへの登録
/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>結果
最後に
実装方法について
Laravelでcommandを作成し、定期実行すると言うのは初めてだったので結構楽しかったです。アクセストークンは定期実行、投稿の取得は毎回APIを実行という方法を今回取っていたのですが、
APIリクエストの節約の為に1日1回夜中に投稿を取得するようにし,その結果をjsonファイルとして出力,またはDBに保存するなどすればリクエスト数を節約出来ると思いました。
もっとこう言う良い方法ある等あれば教えてもらえると嬉しいです。Instagram Basic Display APIについて
自分のブログなどにインスタで投稿したものを表示させたいなら、Instagram Basic Display API
で十分そうですね。今回は「InstagramのAPI使いたい!」と思ってやったのですが,よくよく考えたら僕自身が映える写真を投稿しているアカウントを持っていなかったので,今回のAPIだといい感じの写真が表示されませんでした。。。
- 投稿日:2020-09-21T02:00:00+09:00
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で用意できるなら都度読み替えていただければ実施が可能だと思う。
- 公式ドキュメントと一冊の技術書を元に本記事を記載する。
- Laravel 7.x
- PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応
- 本記事はシリーズとして内容を分割する予定である。記事のタグ「miriwo_laravelチュートリアル」を本シリーズの記事に付与するのでそのほかの記事がみたい方は活用していただきたい。
- DBへの意図しないデータの流入を防ぐためバリデーションを実装する。
- 問い合わせの内容は「入力がされていること」のバリデーションルールを、新規投稿と投稿編集は「入力されており、かつ140文字以内であること」定義してバリデートする。
- バリデーションで弾かれた時にエラーが出力される様にする。
- 下記に目を通しておくと理解が早いかもしれない。
- バリデーションルールは下記の公式ドキュメントに数多く記載されているので実装できたら遊んでみるのもいいかもしれない。
この記事の読後感
- ユーザが入力する部分にバリデーションを実装することができ、バリデーションルールをRequestファイルに記載する。
概要
- Requestファイルの作成と記載
- コントローラファイルの記載修正
- 確認
詳細
Requestファイルの作成と記載
laravel_crudディレクトリで下記コマンドを実行し専用のリクエストファイルを作成する。
$ php artisan make:request ContentRequestlaravel_crudディレクトリで下記コマンドを実行し先に作成したリクエストファイルを開く。
$ vi app/Http/Requests/ContentRequest.php先に開いたリクエストファイルを下記の様に修正する。
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'], ]; } }laravel_crudディレクトリで下記コマンドを実行し専用のリクエストファイルを作成する。
$ php artisan make:request InquiryRequestlaravel_crudディレクトリで下記コマンドを実行し先に作成したリクエストファイルを開く。
$ vi app/Http/Requests/InquiryRequest.php先に開いたリクエストファイルを下記の様に修正する。
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'], // 上記までを追記する ]; } }コントローラファイルの記載修正
laravel_crudディレクトリで下記コマンドを実行しコントローラファイルを開く。
$ vi app/Http/Controllers/ContentController.php先に開いたファイルを下記の様に修正する。
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'); } }laravel_crudディレクトリで下記コマンドを実行しコントローラファイルを開く。
$ vi app/Http/Controllers/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; // 下記を追記する 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')); } }確認
laravel_crudディレクトリで下記コマンドを実行しローカルサーバを起動する。
$ php artisan serve下記にユーザ認証後、下記にアクセスする。
何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。
下記にユーザ認証後、下記にアクセスする。(コンテンツのidは1以外でも構わない)
何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。
下記にユーザ認証後、下記にアクセスする。(コンテンツのidは1以外でも構わない)
何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。
参考文献
- 投稿日:2020-09-21T01:59:36+09:00
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で用意できるなら都度読み替えていただければ実施が可能だと思う。
- 公式ドキュメントと一冊の技術書を元に本記事を記載する。
- Laravel 7.x
- PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応
- 本記事はシリーズとして内容を分割する予定である。記事のタグ「miriwo_laravelチュートリアル」を本シリーズの記事に付与するのでそのほかの記事がみたい方は活用していただきたい。
- DBへの意図しないデータの流入を防ぐためバリデーションを実装する。
- 問い合わせの内容は「入力がされていること」のバリデーションルールを、新規投稿と投稿編集は「入力されており、かつ140文字以内であること」定義してバリデートする。
- バリデーションで弾かれた時にエラーが出力される様にする。
- 下記に目を通しておくと理解が早いかもしれない。
- バリデーションルールは下記の公式ドキュメントに数多く記載されているので実装できたら遊んでみるのもいいかもしれない。
この記事の読後感
- ユーザが入力する部分にバリデーションを実装することができる。
概要
- バリデーションの実装
- エラー出力部分とデフォルト値の設定
- 確認
詳細
バリデーションの実装
laravel_crudディレクトリで下記コマンドを実行してコントローラファイルを開く。
$ vi app/Http/Controllers/ContentController.php先に開いたコントローラファイルのsaveアクションを下記の様に修正する。
laravel_crud/app/Http/Controllers/ContentController.phppublic 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'); }先に開いたコントローラファイルのupdateアクションを下記の様に修正する。
laravel_crud/app/Http/Controllers/ContentController.phppublic 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'); }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'); } }laravel_crudディレクトリで下記コマンドを実行してコントローラファイルを開く。
$ vi app/Http/Controllers/InquiryController.php先に開いたコントローラファイルのsendアクションを下記の様に修正する。
laravel_crud/app/Http/Controllers/InquiryController.phppublic 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')); }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')); } }laravel_crudディレクトリで下記コマンドを実行しローカルサーバを起動する。
$ php artisan serve下記にユーザ認証後、下記にそれぞれアクセスし内容を入力せずに送信ボタンをクリックし、ページ遷移せず同じページが再度読み込まれることを確認する。編集画面での確認はidが1以外のものでも良い。
エラー出力部分とデフォルト値の設定
laravel_crudディレクトリで下記コマンドを実行して新規投稿画面のビューファイルを開く。
$ vi resources/views/contents/input.blade.php開いたファイルを下記の様に修正する。
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>laravel_crudディレクトリで下記コマンドを実行して投稿編集画面のビューファイルを開く。
$ vi resources/views/contents/edit.blade.php開いたファイルを下記の様に修正する。
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>laravel_crudディレクトリで下記コマンドを実行して投稿編集画面のビューファイルを開く。
$ vi resources/views/contents/edit.blade.php開いたファイルを下記の様に修正する。
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>laravel_crudディレクトリで下記コマンドを実行して問い合わせ送信画面のビューファイルを開く。
$ vi resources/views/inquiries/input.blade.php開いたファイルを下記の様に修正する。
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>確認
laravel_crudディレクトリで下記コマンドを実行しローカルサーバを起動する。
$ php artisan serve下記にユーザ認証後、下記にアクセスする。
何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。
下記にユーザ認証後、下記にアクセスする。(コンテンツのidは1以外でも構わない)
何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。
下記にユーザ認証後、下記にアクセスする。(コンテンツのidは1以外でも構わない)
何も入力せず「送信」をクリックし下記のようにエラーが出ることを確認する。
参考文献
- 投稿日:2020-09-21T00:44:45+09:00
冴えないAWS環境の育てかた α を育ててみた�<準備編>
はじめに
クラスメソッドさんの技術ブログ「Developers.io」にてこの様な記事が投稿されました
https://dev.classmethod.jp/articles/saenai-aws-1/この様な稀によく見るAWS環境を「Well Architected Framework」を意識しつつ、
- AWSアカウントの育てかた
- ネットワークの育てかた
- サーバーの育てかた
- データストアの育てかた
の4つに分けて育てていくという記事です。
今日から約1〜2ヶ月間、力試しという形で自分なりに+αでできることも加えつつ、このアーキテクチャに挑戦してみます!
準備編
今日は準備編ということで、元となるアーキテクチャを設計していこうと思います。
デプロイ対象のアプリケーションはフレームワークの練習用に作成した。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 httpdcomposerのインストール
$ curl -sS https://getcomposer.org/installer | php # パスを通す $ sudo mv composer.phar /usr/local/bin/composergitからアプリを持ってくる
$ sudo yum install git $ cd /var/www $ git clone https://github.com/mkoki0422/Laravel_keiziban.gitApacheの設定
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 httpdvendorディレクトリの作成
$ composer install.envファイルの作成
#.envファイルの作成 $ cp .env.example .env # .envのAPP_KEYの生成 $ php artisan key:generateRDS
サブネットグループ作成
RDSを構成する前にRDSを配置するサブネットグループを作成しましょう。
マネジメントコンソールの「サブネットグループ」から「DBサブネットグループの作成」に進みましょう。
注意 RDSはMultiAZ構成をしない場合でも、2つ以上のAZを指定しないといけません!データベース用セキュリティグループ
DBサーバーに適用するためのセキュリテイグループも事前に作成しておきます。
インバウンドルールのタイプを「MYSQL/Aurora」、ソースはEC2で適用しているセキュリティグループを選択する方法と、IPアドレスを選択する方法がありますが、今回はセキュリティグループをソースとして指定します。
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サーバーを複製します。
使ってなかったドメインがあったので、複数値ルーティングで2つのWEBサーバーをルーティングさせました。
明日の予定
今日は準備編ですので、とりあえずデプロイして、RDSに接続、Route53でルーティングさせるところまでやってみました。
明日は
- Route53 / ヘルスチェックの有効化
- CloudFront / ディストリビューションの作成
- CloudFront / 追加のメトリックを有効化
- CloudFront / アクセスログの取得
- CloudFront / 特定地域からのアクセスをブロック
- CloudFront / カスタムエラーレスポンスとしてSorry Pageを構成
- CloudFront / 監視
- ACM / サーバー証明書の作成
- ACM / CloudFrontへの関連付けと適切なセキュリティポリシーの選択
を進めていこうかと思いますー!