- 投稿日:2020-03-21T22:28:11+09:00
PHPの時間関連の関数について
PHPの時刻関連の処理でわからないことがあったので調べた。
date関数
ローカルの日付・時間を書式化する。
指定された引数(timestamp)を与えられた文字列によってフォーマットし、日付文字列を返す。
タイムスタンプが与えられない場合は、現在の時刻が利用される。タイムスタンプのデフォルト値は現在のUnixタイムスタンプを返すtime関数となっている。説明
date ( string $format [, int $timestamp = time() ] )引数
format
一つ目の引数である
format
で出力される文字列の書式を決定する。timestamp
Unixタイムスタンプを渡す。
返り値
日付を表す文字列が返される。timestampに数字以外が使用された場合は、FALSEが返される。
利用例
echo date("m.d.y"); // 3.21.20 echo date("H:i:s"); // 12:40:22 echo date("F j, Y, g:i a"); // March 21, 2020, 12:40 pm echo date("l"); // Saturday echo date('l \t\h\e jS'); // Saturday the 21stmktime関数
日付をUnixタイムスタンプとして取得する。
説明
mktime ([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] )引数
hour
時刻。負の値の場合は、その日の0時から遡った時刻となる。
minute
分。負の値の場合は、その前の時刻が表される。59より大きい場合はその次の時刻以降が表される。
second
秒。
month
月。
day
日数。
year
年。
is_dat
このパラメータはサマータイム (DST) の時に1にセットされ、 そうでない時に0、サマータイムであるかどうかが不明である場合に-1 にセットされる。
返り値
Unixタイムスタンプが返される。
利用例
echo mktime(0, 0, 0, 3, 0, 2000); // 95178240time関数
現在の Unix タイムスタンプを返す。マイクロ秒の単位まで返したいときには、
microtime()
を利用する。利用例
echo time(); // 1584796017 $now = time(); echo date("F j, Y, g:i a", $now); // March 21, 2020, 1:08 pmgetdate関数
日付/時刻情報を取得する
説明
getdate ([ int $timestamp = time() ] )引数
timestamp
timestamp が指定されなかった場合のデフォルト値は、 現在の時刻
返り値
timestamp に関連する情報を連想配列で返す。
利用例
$today = getdate(); print_r($today); <!--出力 Array ( [seconds] => 27 [minutes] => 25 [hours] => 13 [mday] => 21 [wday] => 6 [mon] => 3 [year] => 2020 [yday] => 80 [weekday] => Saturday [month] => March [0] => 1584797127 )-->
- 投稿日:2020-03-21T22:20:38+09:00
LravelのDBテーブル作成&データ投入
お約束事
本記事で発信される情報は、正確性、完全性、有用性、その他の事項について一切責任を負いかねます。自己判断にてご活用ください。
全体の流れ
Migrationを行いテーブルを作成する
- migrationコマンドを実行するための空ファイルの作成
- 作成されたmigrationファイルをテーブル構造通りに記述する
- migrateコマンドで実際にテーブルを作成する
- 型変更、カラム追加・削除等
Sheederを用いてテーブルデータを投入する
- seedを実行するための空ファイルの作成
- 作成されたseedファイルを投入したいデータに合わせて記述する
- seedコマンドで実際にテーブルにデータを投入する
Migrationでテーブルを作成する
1、migrationするための空ファイルの作成
ターミナルでの作業$php artisan make:migration {テーブル名} Created Migration: {年}_{月}_{日}_{時間}_{テーブル名}{プロジェクトRoot}/database/migrationsにファイルが作成される。
2、作成されたmigrationファイルをテーブル構造通りに記述する
作成されたmigrationファイル<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class AppSystem extends Migration { /** * Run the migrations. * * @return void */ public function up() { //{}は不要 Schema::create('{テーブル名}', function (Blueprint $table) { $table->bigIncrements('id');//インクリメントするbigInt $table->boolean('is_maintenance');//bool型 $table->text('app_version');//text $table->dateTime("created_at");//dateTime $table->dateTime("updated_at");//dateTime }); } /** * Reverse the migrations. * * @return void */ public function down() { //{}は不要 Schema::dropIfExists('{テーブル名}'); } }3、migrateコマンドで実際にテーブルを作成する
ターミナルでの作業$php artisan migrate Migrating: 2020_03_21_115544_app_system Migrated: 2020_03_21_115544_app_system (0.05 seconds)※Dockerの場合は、workspaceにログインして実行しないとErrorになります。
作業後にDBにテーブルが作成されていること、migrationsテーブルに実行履歴が登録されていることが確認できる。
4、型変更、カラム追加・削除等
型変更
ターミナルでの作業php artisan make:migration change_{対象カラム名}_{テーブル名}_table --table={テーブル名} Created Migration: 2020_03_21_141300_xxxxxxxxxxxxxxx※何をしたかわかりやすい名前がよいと思います。
migrateファイルを修正public function up() { Schema::table('{テーブル名}', function (Blueprint $table) { //新しい型 $table->text('{カラム名}')->change(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('{テーブル名}', function (Blueprint $table) { //変更前の型 $table->string('{カラム名}')->change(); }); }カラム追加、削除
ターミナルでの作業php artisan make:migration add_{対象カラム名}_{テーブル名}_table --table={テーブル名} Created Migration: 2020_03_21_141300_xxxxxxxxxxxxxxx※何をしたかわかりやすい名前がよいと思います。
migrateファイルを修正(削除はupとdown内の動作を入れ替えればOK)public function up() { Schema::table('{テーブル名}', function (Blueprint $table) { //カラム追加 afterで指定したカラムの後ろに追加できる $table->string('{カラム名}')->after('id'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('{テーブル名}', function (Blueprint $table) { //カラム削除 $table->dropColumn('{カラム名}'); }); }Seederでのデータ投入
1、seedを実行するための空ファイルの作成
ターミナルでの作業$php artisan make:seeder {ファイル名} Seeder created successfully.{プロジェクトRoot}/database/seedsにファイルが作成される
ターミナルでの作業$php composer dump-autoload Discovered Package: beyondcode/laravel-dump-server Discovered Package: encore/laravel-admin Discovered Package: fideloper/proxy Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Package manifest generated successfully.2、 作成されたseedファイルを投入したいデータに合わせて記述する
作成されたseederファイルを修正<?php use Illuminate\Database\Seeder; use App\Libs\Common; class LevelSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { //Jsonファイルを読み込みdecodeしてくれる自作クラスを呼び出す。 //SpreadSheetで作成したマスターデータをJson化したデータが配置してある。 $dataArray = Common::getJsonFile('{jsonFile名}'); //Jsonデータを1件ずつ挿入 foreach ($dataArray as $value){ $insertData = array(); foreach ($value as $key => $data) { $insertData[$key] = $data; } DB::table('level')->insert([ $insertData ]); } } }Jsonファイルを読み込みdecodeするクラス<?php namespace app\Libs; class Common { public static function getJsonFile($fileName){ $url = public_path() . '/json/'.$fileName.'.json'; $json = file_get_contents($url); $json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN'); $arr = json_decode($json, true); return $arr; } }※ファイルの存在チェックとかはしておりません。必要であればしてください。
※本番環境にjsonファイルはアップしないので、このまま本番にアップするとマスターデータ丸見え等
セキュリティ的に良くないので.gitignore等で除外してください。3、seedコマンドで実際にテーブルにデータを投入する
ターミナルでの作業(任意のテーブルに実行)$php artisan db:seed --class={1で作成したクラスを指定} Database seeding completed successfully.※数百件レベルのデータなら秒で終わるので負荷とかは検討していません。
- 投稿日:2020-03-21T18:59:54+09:00
【fullcalendar】Laravelとfullcalendar(v4)を使って予約管理システムを作った話【Laravel】
仮想案件
家庭教師予約システムをつくる仕様概要
・家庭教師派遣会社は先生(学生バイト達)をAdmin管理画面からスケジューリング
・会員(生徒)は任意の先生を管理画面から予約
・派遣会社の画面と会員の画面はそれぞれ別の認証経路(マルチAuth)
・UIはGoogleカレンダー風を希望前提
・fullcalendarメインの記事になります
・Laravelの基本を理解している方向けです
・非同期通信の基本も理解している方向けです
・データベース設計の基本も理解している方向けです
要はfullcalendarにポイントを置いてます。目次
- 完成イメージ
- LaravelのマルチAuth対応
- 管理画面の作成
- fullcalendarの組み込み
- ビジネスロジックの実装
- 補足
1.完成イメージ
家庭教師派遣会社が使用するAdmin管理画面(週表示)
カレンダー左横の先生の箱をカレンダーにドロップしてイベントを作成
会員(生徒)が使用する予約画面(週表示)
任意のイベント(先生)をクリックして予約処理をする
2.LaravelのマルチAuth対応
派遣会社と会員(生徒)はそれぞれのログイン認証を経ることになるのでその対応です。
既存のguardとproviderに加え、admin用のguardとproviderを作成します。
このあたりは良記事が既に存在するのでそちらを参照してください
(いきなり参照ですいません;)Laravel6 備忘録 −ユーザー認証(Auth)−
【Laravel】マルチログイン(ユーザーと管理者)機能3.管理画面の作成
左にメニュー、右にコンテンツエリアがあるオーソドックスなレイアウトです。
組み方はこちらを参照してください。
(またまた参照ですいません…しかも手前味噌で;)【Laravel】デフォルトの管理画面に左メニューをサクッと設置
4.fullcalendarの組み込み
まずadminから。今回はadmin用のテンプレにてCDNで読み込んでます。
(月表示、週表示、デイリー、リスト、日本語対応)
fullcalendar V3だとapp.jsとぶつかるらしいですが、V4では特に問題ありませんでした。※admin用の外枠テンプレートをadmin_app.blade.phpとして作った場合
/resources/views/layouts/admin_app.blade.php<link href='https://unpkg.com/@fullcalendar/core@4.3.1/main.min.css' rel='stylesheet' /> <link href='https://unpkg.com/@fullcalendar/daygrid@4.3.0/main.min.css' rel='stylesheet' /> <link href='https://unpkg.com/@fullcalendar/timegrid@4.3.0/main.min.css' rel='stylesheet' /> <link href='https://unpkg.com/@fullcalendar/list@4.3.0/main.min.css' rel='stylesheet' /> <script src='https://unpkg.com/@fullcalendar/core@4.3.1/main.min.js'></script> <script src='https://unpkg.com/@fullcalendar/interaction@4.3.0/main.min.js'></script> <script src='https://unpkg.com/@fullcalendar/daygrid@4.3.0/main.min.js'></script> <script src='https://unpkg.com/@fullcalendar/timegrid@4.3.0/main.min.js'></script> <script src='https://unpkg.com/@fullcalendar/list@4.3.0/main.min.js'></script> <script src='https://unpkg.com/@fullcalendar/core/locales/ja'></script>ビューファイルを作成します。
今回は/resources/views/admin/schedule/calendar.blade.phpで作成。
html部分はこんな感じです。calendar.blade.php<div class="container"> <div class="row justify-content-center"> <!-- left --> @include('admin.menu') <div class="col-md-10"> <div class="card"> <div class="card-header"><i class="fas fa-id-card"></i> カレンダー共有</div> <div class="card-body"> @if (session('status')) <div class="alert alert-success" role="alert"> {{ session('status') }} </div> @endif <div id='external-events'> <p><strong>先生</strong></p> @foreach ( $data as $d ) <div class="fc-event">{{ $d->teacher_name }}<span style="opacity:0;">:{{ $d->id }}</span></div> @endforeach <p style="display:none;"> <input type='checkbox' id='drop-remove' /> <label for='drop-remove'>remove after drop</label> </p> </div> <div id='calendar-container'> <div id='calendar'></div> </div> </div><!-- end card-body --> </div><!-- end card --> </div> </div> </div>
fc-event
div要素(カレンダー左に列挙されてるドラッグ&ドロップ可能要素)には今回先生の名前が入るので、Controllerでフェッチしたデータをまわして動的に配置してます。同div内直後の<span style="opacity:0;">:{{ $d->id }}</span>
はeventReceive時に先生IDをイベントに登録するための苦肉の策です;不可視化してコロンの後に入れといて取り出してます;var arr = info.event.title.split(':'); info.event.setExtendedProp('teacher_id', arr[1]);続いて同ファイル内のjs部分です。fullcalendarのインスタンス作成とプロパティやらコールバックを定義します。各コールバックにビジネスロジックを記述した関数を置いていく感じになると思います。eventRenderでクリック間隔にてシングルクリックとダブルクリックを判定してるのがなんかあれですが…
events:{url}
はデータソースの取得元です。通常だとDBから取得したデータをJSONにしてエコーする処理になるでしょう。ちなみにメソッドはGET
限定です。calendar.blade.phpdocument.addEventListener('DOMContentLoaded', function() { var Calendar = FullCalendar.Calendar; var Draggable = FullCalendarInteraction.Draggable; var containerEl = document.getElementById('external-events'); var calendarEl = document.getElementById('calendar'); var checkbox = document.getElementById('drop-remove'); // initialize the external events new Draggable(containerEl, { itemSelector: '.fc-event', eventData: function(eventEl) { return { title: eventEl.innerText }; } }); // initialize the calendar var calendar = new Calendar(calendarEl, { plugins: [ 'interaction', 'dayGrid', 'timeGrid','list' ], header: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek' }, allDaySlot: false, forceEventDuration : true, eventColor: 'lavender', defaultTimedEventDuration: '01:00', defaultView: 'timeGridWeek', slotDuration: '00:10:00', minTime : '10:00', maxTime : '22:10', locale : 'jaLocale', editable: true, selectable: true, allDaySlot: false, droppable: true, // this allows things to be dropped onto the calendar buttonText: { today:'今日', month:'月', week: '週', day: '日', list: 'リスト' }, events:'/admin/events/source', select: function (info) { // カレンダーセルクリック、範囲指定された時のコールバック console.log('select'); }, eventReceive: function(info) { // イベントがexternal-eventからドロップされた時のコールバック console.log('eventReceive'); }, eventDrop: function(info) { // イベントがドロップされた時のコールバック console.log('eventDrop'); }, eventResize: function(info) { // イベントがリサイズ(引っ張ったり縮めたり)された時のコールバック console.log('eventResize'); }, eventRender: function (info) { //wired listener to handle click counts instead of event type info.el.addEventListener('click', function() { clickCnt++; if (clickCnt === 1) { oneClickTimer = setTimeout(function() { clickCnt = 0; // SINGLE CLICK console.log('single click'); }, 400); } else if (clickCnt === 2) { clearTimeout(oneClickTimer); clickCnt = 0; // DOUBLE CLICK console.log('double click'); } }); } }) calendar.render(); });cf. events:に指定したURLのサンプル
/admin/events/source.php// テストとしてイベントを3つ作成してみる $data = []; $ev1 = ['id'=>'1','teacher_id'=>'18','title'=>'event1','start'=>'2020-03-17T10:00:00','color'=>'lightpink']; $ev2 = ['id'=>'2','teacher_id'=>'20','title'=>'event2','start'=>'2020-03-18T10:30:00','color'=>'lightgreen']; $ev3 = ['id'=>'3','teacher_id'=>'35','title'=>'event3','start'=>'2020-03-18T10:50:00','color'=>'yellow']; array_push($data,$ev1,$ev2,$ev3); echo json_encode($data);
teacher_id
はfullcalendarのイベントモデルではデフォルトのプロパティではないですが、未定義のプロパティはextendedProps
の下に生やしてくれます。複数系なのでタイポ注意です。値の更新はsetExtendedProp
です。// infoはコールバック時に渡されるオブジェクト // アクセス info.event.extendedProps.teacher_id // 値変更 info.event.setExtendedProp('teacher_id',40);5.ビジネスロジックの実装
ここまでくればあとはゴリゴリとビジネスロジックを書いていくだけです。
非同期にてDB更新してイベントの見た目を変更するような処理がメインになるでしょう。calendar.blade.php// eventReceive時に走る処理のサンプル // 略 eventReceive: function(info) { Create(info); }, // 略 const Create = (info) => { var dt = new Date(); info.event.setExtendedProp('identifier',dt.getTime()); // csrf。Laravelお約束 $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); $.ajax({ type: 'post', data: { 'identifier': info.event.extendedProps.identifier, 'teacher_id': info.event.extendedProps.teacher_id, 'start': info.event.start, 'end': info.event.end }, datatype: 'json', url: '/admin/update' /* identifierをキーに登録or更新 */ }) .done(function(data){ json = JSON.parse(data); if ( json['result'] == 'success' ) { // サーバサイドにて設定された背景色に変更 info.event.setProp('color',json['color']); } }) .fail(function(data){ alert('error'); }); }sample(/admin/update)use App\Event; // Eventモデル使用 $event = Event::firstOrNew(['identifier'=>$request->identifier]); $event->teacher_id = $request->teacher_id; $event->start = date('Y-m-d H:i',strtotime(strstr($request->start,'GMT',true))); $event->end = date('Y-m-d H:i',strtotime(strstr($request->end,'GMT',true))); DB::transaction(function() use ($event) { try { $event->save(); } catch (\Exception $e) { // エラー処理 } }); echo json_encode(array('result'=>'success','color'=>'yellow'));生徒側のカレンダーも基本adminと一緒でよいのですが認証経路が違うので処理内容が一緒でも
events:{url}
の部分はメソッドをコピペして移植するなどの変更が必要です。それとイベントのドロップ&ドラッグのUIは必要ないので、該当箇所を削除します(html部分のexternal-events要素も忘れずに)。calendar.blade.phpdocument.addEventListener('DOMContentLoaded', function() { // 略 /* ここから var Draggable = FullCalendarInteraction.Draggable; var containerEl = document.getElementById('external-events'); var calendarEl = document.getElementById('calendar'); var checkbox = document.getElementById('drop-remove'); // initialize the external events new Draggable(containerEl, { itemSelector: '.fc-event', eventData: function(eventEl) { return { title: eventEl.innerText }; } }); ここまで削除 */ // 略 events:'/user/events/source', /* /admin/events/sourceではNG */6.補足
イベントのクリアと再取得
週送りや月を送るとfullcalendarはイベントデータを都度イベントソースからフェッチしてくるのでイベントをクリアしてやらないと重複して表示される現象にみまわれました。以下はその対応です。キレイじゃない方法ですが載せておきます;calendar.blade.php// gCalendarはcalendarの参照 $(document).on('click','.fc-next-button,.fc-prev-button,.fc-dayGridMonth-button,.fc-timeGridWeek-button,.fc-timeGridDay-button,.fc-listWeek-button', function () { events = gCalendar.getEvents(); var len = events.length; for (var i = 0; i < len; i++) { events[i].remove(); } eventSources = gCalendar.getEventSources(); var len = eventSources.length; for (var i = 0; i < len; i++) { eventSources[i].remove(); } gCalendar.addEventSource('/admin/events/source'); gCalendar.refetchEvents(); });イベントIDについて
イベントモデルではデフォルトでid
が用意されていますが、クライアント処理でsetPropによる値の変更はできないようです。スタックオーバーフローではバグではないのかという議論がされていましたが、おそらく仕様だと思われます。
fullcalendarがイベント作成時に自動で採番してくれるわけではないのでextnededPropsでしのぎました。calendar.blade.phpvar dt = new Date(); info.event.setExtendedProp('identifier',dt.getTime());最後に
ここで紹介したのはほんの一部です。
いろいろできるので公式のぞいてみてください。
- 投稿日:2020-03-21T18:52:11+09:00
echoとprintの違い
PHPの
echo
とecho 1, 2, 3; // ← syntax error ではない print 1, 2, 3; // ← これは syntax error echo print 1; // ← syntax error ではない print echo 1; // ← これは syntax error形式的に言うと
echo
と
- まれに「標準出力する」と言及されますが、それは間違いです
fwrite(STDOUT, $str)
の実行結果とは一致しないということですecho
は文ですがecho
は複数の引数をとりますが、- 便宜上関数マニュアルに掲載されているが関数ではありません
どれを使えばいいの?
好みで何を使ってもいいと思うのですが、
echo
を薦めます。echo $string;または
echo $str1, $str2, PHP_EOL;この形式だけを使えばいいと思います。
また、
echo
とecho($v)
やprint($v)
のような関数呼び出し風の記述は誤解を呼ぶので使わない方が良いと思います。echo
echo — 1 つ以上の文字列を出力する
echo
がリスト形式の引数を受け付け、返り値を持たないことです。たしかにPHPの文は式の項にならない(値を返さない)ので、説明通りです。
まれに
echo
を関数のように呼び出すひとがいます。echo("str");しかし、この
()
は$i = 1 + 2;
と書いても$i = (1 + 2);
と書いても結果が変わらないのと同じで、余分なカッコを書いているだけです。これで、以下のような式がsyntax errorになることを説明できます。
echo("str1", "str2");これは関数呼び出しで例えると
f(1, 2)
と書くべきものをf((1, 2))
と書いていることと同じです。PHPには関数呼び出しや言語構造の引数以外に(1, 2)
のように値を列挙する式はありません。print — 文字列を出力する
echo
との主な違いは、この説明からはわかりにくいですが、
どうしてわざわざ、
echo
とは異なるこのような仕様があるのでしょうか。わたしが考えつく限り、この仕様のユースケースは以下のようなものです。
foo() and print "success\n"; bar() or print "error\n";これらの式は
if
を使って以下のように書き直せます。if (foo()) { echo "error", PHP_EOL; } if (!bar()) { echo "error", PHP_EOL; }これらは、わざわざショートコーディングを正当化するためには心許ない例です。書き捨てのコードでデバッグ用途で一瞬使うだけならともかく、もっとわかりやすく書いた方がよいでしょう。
print(1)
と書いても(print 1)
と書いても同じことです。print(1, 2)
と書いてしまうと関数呼び出しのように錯覚させてしまいますが、これは関数呼び出し式ではないため、純粋にsyntax errorです。printf()
せっかくなので
printf()
にも言及しておきましょう。echo
とprintf()
はPHPの組み込み関数です。printf — フォーマット済みの文字列を出力する
これも出力バッファに文字列を書き出すという意味では共通していますが、意味が異なります。
echo
とprintf()
はフォーマット文字列を整形する、一種のテンプレートエンジンと呼べるものです。printf('%d + %d = %d', $a, $b, $a + $b);
printf()
は高機能なので活用すると便利ですが、echo
や// 安全なコード echo "こんにちは {$name} さん! ({$num}回め)"; // 危険なコード printf("こんにちは {$name} さん! (%d回め)", $num);後者のコードでは
$name
に%
が含まれると実行時エラーやテンプレートの位置をずらされるといった問題が起きえます。sprintf()
を使って文字を組み立てる場合などもバグや脆弱性の原因となりうることがあるでしょう。printf("こんにちは %s さん! (%d回め)", $name, $num);
printf()
やsprintf()
などの第一引数には変数を直接展開せず、printf()
の%s
などを使ってください。オペコードとの関係
PHP5時代はオペコードレベルで
ECHO
は別の命令でしたが、PHP7ではECHO
命令に統合されており最適化も施されるので特に違いは生じないはずです。2015年9月に出版されたPHPはどのように動くのか ~PHPコアから読み解く仕組みと定石では
echo
の方がパフォーマンスに優れていると読み取れる記述があるのですが、実際には2015年12月にはPHP 7.0がリリースされており、実態にそぐわない記述になってしまいました。読者への課題
PHP-Parserを使って、以下のようなソースコードを生成してみましょう。
<?php echo 1; print 2;
- 投稿日:2020-03-21T18:01:21+09:00
Fuelを使ってパラメータ付きPOSTリクエストを送信した
なんとかHTTPリクエストをAndroidのエミュレーターからローカルホストのLaravelサーバーに送信することができたので投稿します。
ライブラリのインストール
app/build.gradle
にインストールするライブラリを書いていきます。app/build.gradledependencies { // Fuel for HTTP Connections implementation 'com.github.kittinunf.fuel:fuel:2.2.0' implementation 'com.github.kittinunf.fuel:fuel-gson:2.2.0' implementation "com.squareup.moshi:moshi:1.5.0" implementation "com.squareup.moshi:moshi-kotlin:1.5.0" }HTTPリクエストを送信するための
Fuel
と,JSONのパラメータを作成するためのmoshi
をインストールします。
(僕は最初,app/build.gradle
じゃなくてルートのgradleにimplementation
を書いちゃって動きませんでした...。)パラメータのフォーマットを決める
JSONのパラメータのフォーマットを決めるためのクラス
SampleRequestFormat
クラスを作成します。SampleRequestFormat.ktclass SampleRequestFormat(val kekey: String)
kekey
という名前のキーを定義しました。Androidの画面作成
画面にボタンが一つ配置されているだけのものです。
activity_debug.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/post_debug_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Button" /> </LinearLayout>リクエスト送信用のアクティビティを作成
POSTでLaravelのサーバーへリクエストをパラメータ付きで送るためのプログラムを作成します。
DebugActivity.ktimport android.os.Bundle import android.widget.Button import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.example.live_barcode_reader.request_format.SampleRequestFormat import com.github.kittinunf.fuel.Fuel import com.squareup.moshi.KotlinJsonAdapterFactory import com.squareup.moshi.Moshi class DebugActivity : AppCompatActivity() { private var button: Button? = null private val URL: String = "http://10.0.2.2:8000" private val domain: String = "/sample9999" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_debug) val targetURL: String = URL + domain val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() val requestAdaper = moshi.adapter(SampleRequestFormat::class.java) button = findViewById(R.id.post_debug_button) button?.setOnClickListener { val header: HashMap<String, String> = hashMapOf("Content-Type" to "application/json") val sampleRequest = SampleRequestFormat( kekey = "from Android" ) Fuel.post(targetURL) .header(header) .body(requestAdaper.toJson(sampleRequest)) .response { _, response, result -> println(response) println(result) } //Toast.makeText(this, "PUSHED", Toast.LENGTH_SHORT).show() } } }このとき,
URL
の中身がhttp://10.0.2.2:8000
であることを覚えておいてください!
最後に,HTTP通信の設定についてAndroidManifest.xml
に記述します。HTTP通信の設定
network_security_config.xml
というファイルを作成し,HTTP通信できるドメインを指定して許可します。network_security_config.xml<?xml version="1.0" encoding="utf-8"?> <network-security-config xmlns:android="http://schemas.android.com/apk/res/android"> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">10.0.2.2</domain> </domain-config> </network-security-config>その後,
AndroidManifest.xml
にも変更を加えます。AndroidManifest.xml<application ... android:networkSecurityConfig="@xml/network_security_config">次はLaravelのサーバーです。
サーバーサイド
web.phpRoute::post('/sample9999', 'TestController@sample9999');
sample9999
というPOSTリクエストに反応してTestController
の中のsample9999()
という関数が反応するようにします。9999という数字は少し理由があって付けました。TestController.php<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class TestController extends Controller { public function sample9999(Request $request) { $json_string = file_get_contents('php://input'); logger($json_string); $json_object = json_decode($json_string); logger(var_export($json_object, true)); logger(gettype($json_object)); logger($json_object->kekey); return 'got request'; } }とりあえずこのコードで動作確認だけしてみました。
動作確認
Androidとサーバーを起動して,エミュレータのボタンを押すと,
laravel.log[2020-03-21 08:59:49] local.DEBUG: {"kekey":"from Android"} [2020-03-21 08:59:49] local.DEBUG: (object) array( 'kekey' => 'from Android', ) [2020-03-21 08:59:49] local.DEBUG: object [2020-03-21 08:59:49] local.DEBUG: from AndroidAndroidのログ↓
I/System.out: <-- 200 http://10.0.2.2:8000/sample9999 Response : OK Length : -1 Body : got request Headers : (11) Cache-Control : no-cache, private Connection : close Set-Cookie : laravel_session=eyJpdiI6IkVrR0RxK241U0xkWkRUbFhWWmhMSXc9PSIsInZhbHVlIjoicEF2d2h5dXVDVFdmVElDc09hLzNvMFJmNmM2bitBbTRrSFBBQjVucEN1elY5eGNUQy9ZZUFqVkExdkplTGEwWSIsIm1hYyI6IjAzMTBiZWEwN2FlYmYxODhlZWQ3OWY2MmU1YTBhMWQ2MWNiYzIzMGI3ZWY1NWM0NjlhYTNkMzkxMzJiZTgyYzIifQ%3D%3D; expires=Sat, 21-Mar-2020 10:59:49 GMT; Max-Age=7200; path=/; httponly; samesite=lax Date : Sat, 21 Mar 2020 08:59:49 GMT, Sat, 21 Mar 2020 08:59:49 GMT X-Android-Selected-Protocol : http/1.1 X-Powered-By : PHP/7.3.11 Content-Type : text/html; charset=UTF-8 Host : 10.0.2.2:8000 X-Android-Received-Millis : 1584781188564 X-Android-Response-Source : NETWORK 200 X-Android-Sent-Millis : 1584781188475 [Success: [B@615227d]HTTPリクエストが送られています!
- 投稿日:2020-03-21T17:23:37+09:00
本日の学習内容part2
参考記事
パスワードの暗号化(sha1)
あるパスワードをDBに登録時に暗号化した(ハッシュ計算)後、ログイン画面で入力したパスワードがDBのパスワード(暗号化済み)と合っているかどうかをチェックする時は暗号化を行う。
login.php
if ($_POST['password'] !== '') {
$login = $db->prepare('SELECT * FROM members WHERE password=?');
$login->execute(array(
sha1($_POST['password'])
));
$member = $login->fetch();
感想
学習した内容を上手く文章に出来なくててもどかしい(笑) これからそのスキルも磨いていきたい。
- 投稿日:2020-03-21T15:53:24+09:00
配列で受け取ったRequestに対してのバリデーション
<table> <form action="/person/create" method="post"> {{ csrf_field() }} <tr> <th>name: </th> <td><input type="text" name="person[name]" value="{{ old('name') }}"></td> </tr> <tr> <th>email: </th> <td><input type="text" name="person[email]" value="{{ old('email') }}"></td> </tr> <tr> <th>age: </th> <td><input type="text" name="person[age]" value="{{ old('age') }}"></td> </tr> <tr> <th></th> <td> <input type="submit" value="send"> </td> </tr> </form> </table>上記のように配列にまとめて
name="person[name]
のような形でフォームを送信する場合バリデーションの書き方が少し変わる
・配列名.name
・配列名.email
上記のような指定方法となる// バリデーション // # requestが配列で送られてくる場合は'配列.要素名'となる public static $rules = array( 'person.name' => 'required', 'person.email' => 'email', 'person.age' => 'integer|min:0|max:150' );
- 投稿日:2020-03-21T15:51:26+09:00
CircleCi2.0でphpunit実行時に Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate... が出たときの対処法
やってもダメだったこと
php.inimemory_limit = -1 // メモリの制限を無くす.circleci/config.yml- image: circleci/mysql:5.7 command: [--max_allowed_packet=32M] // mysqlのパケット制限を増やす解決策
.circleci/config.yml- run: command: vendor/bin/phpunit -d memory_limit=512Mphpunitの実行オプションに-d memory_limit=512Mをつけることで解決しました。
なかなか解決策が見つからなくて、かなりハマってしまいました(汗)
この記事が誰かのお役に立てたら何よりです。参考にさせていただいた記事
https://qiita.com/pinekta/items/4ece0c88402610f874d0
↓こちらに解決策が載ってました。
https://discuss.circleci.com/t/php-memory-size/7602
- 投稿日:2020-03-21T13:38:32+09:00
Laravel ソースコードリーディング サービスコンテナ編 part.1 - bindとmake、buildとresolve -
いい加減サービスコンテナをよく知らずに使うのはよくないな〜ということでLaravelコアのコードを読みつつ、記事を書いて投げてみました。同じようなLaravel初心者の方の参考になれば幸い...
ただ、僕もまだまだ未熟ですので、意図の読み違いなどは普通にあり得ると思います。間違いなどありましたらご指摘くだされば幸いです
基礎知識: サービスコンテナ
DIコンテナとも。Dependency Injection パターンと併用して、依存性を1箇所に集める。
Laravelアプリケーション内における様々なインスタンスの生成を担う。Laravelでは文字通り、これがないとアプリケーションが始まらない。Laravelのコアと言って良い。
FQCNはIlluminate\Foundation\Application
クラスで、Laravel中ではよく$app
というプロパティとして現れる。
Illuminate\Foundation\Application
クラスはIlluminate\Container\Container
クラスを継承しており、コンテナの結合処理などを担うのはこちらのクラスなので、最初はこっちを見ることになる。用語
結合(bind)
キーと解決処理をペアでコンテナに登録すること。
結合にはbind
、singleton
、instance
などのメソッドを使う。呼び出し時に使用されるキーには大抵FQCNが使用されるが、極端な話文字列であればなんでも良い。ファサードの場合はわかりやすく短い文字列が使われやすい(DBファサードなら
db
とか)。具象クラスの場合、Laravelは勝手にそのクラスを探し出しインスタンス化してくれるので、結合処理を書く必要性は特にない(はず)。言い換えると、後述の処理内容を見ればわかりますが、コンテナに結合されていないキーで問い合わせた場合、そのキーと一致するクラスを探し、インスタンスを生成しようとする処理が走ることになる。
結合処理は大抵、サービスプロバイダ内で行われる。
解決(resolve)
結合されたインスタンス化の方法を元に、インスタンスを生成すること。
明示的な解決には基本的にmake
メソッドを使用する。実際の処理
doc以外のコメントは省略しています。
build
による具象クラスの解決処理順序的にはまず
build
メソッドの、実際に与えられたクラス名からインスタンスを生成する役割を理解しておくと良さそう。ReflectionClass
とReflectionParameter
に関してはわからない方はPHPマニュアルを参照してください。Container.php/** * Instantiate a concrete instance of the given type. * * @param string $concrete * @return mixed * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function build($concrete) { // $concreteがクロージャなら、実行してその結果を返す。 if ($concrete instanceof Closure) { return $concrete($this, $this->getLastParameterOverride()); } try { $reflector = new ReflectionClass($concrete); } catch (ReflectionException $e) { throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e); } if (! $reflector->isInstantiable()) { return $this->notInstantiable($concrete); } $this->buildStack[] = $concrete; $constructor = $reflector->getConstructor(); if (is_null($constructor)) { array_pop($this->buildStack); return new $concrete; } $dependencies = $constructor->getParameters(); try { $instances = $this->resolveDependencies($dependencies); } catch (BindingResolutionException $e) { array_pop($this->buildStack); throw $e; } array_pop($this->buildStack); return $reflector->newInstanceArgs($instances); }重要なのは、リフレクションを使った処理以降の部分。後述する
bind
の処理と合わせて理解して欲しい部分ですが、build
メソッドの$concrete
には、具象クラスのFQCNか、解決方法となるクロージャが渡されることが想定されている。
よって、まずインスタンス化できない( = abstract class or interface)場合、notInstantiable
メソッドでBindingResolutionException
を投げる。具象クラスのFQCNが渡されている(インスタンス化が可能である)場合、
$buildStack
の末尾に$concrete
をコピーしたのち、$concrete
のコンストラクタを取得する。ここでコンストラクタが取得できない場合(=定義されていない場合)、Laravelでは他のクラスに依存していないことを意味するようで(Laravelコアではコンストラクタインジェクションを使う前提なのですね)、そのまま
$concrete
をインスタンス化して返却する。コンストラクタが取得できた場合、他のクラスへの依存関係をもっていると判断し、コンストラクタの引数を配列として取得、それを
resolveDependencies
メソッドの引数に与え実行、その結果依存関係をもつクラスのインスタンス群である$instances
が返される。
resolveDependencies
メソッドの実装はコンテキストによる結合とかも絡んできて長くなるので今回はばっさり省略したいところですが、その処理の途中で、引数のタイプヒントがクラスだった場合、そのインスタンス化を行うresolveClass
メソッドの中でmake
メソッドが呼ばれるということだけは、後述の処理の関係もあり押さえておいた方が良い。Container.php/** * Resolve a class based dependency from the container. * * @param \ReflectionParameter $parameter * @return mixed * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function resolveClass(ReflectionParameter $parameter) { try { return $this->make($parameter->getClass()->name); } catch (BindingResolutionException $e) { if ($parameter->isOptional()) { return $parameter->getDefaultValue(); } throw $e; } }最後に、その
$instances
を引数として与えた上で、ReflectionClass::newInstanceArgs
メソッドを実行。結果として、$concrete
に指定されたクラスのインスタンスが依存性を解決した上で生成、返却され、build
メソッドの処理が終了する。
bind
,make
による結合と解決
bind
による結合結合の基本となる
bind
の処理から。Container.php/** * Register a binding with the container. * * @param string $abstract * @param \Closure|string|null $concrete * @param bool $shared * @return void */ public function bind($abstract, $concrete = null, $shared = false) { $this->dropStaleInstances($abstract); if (is_null($concrete)) { $concrete = $abstract; } if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact('concrete', 'shared'); if ($this->resolved($abstract)) { $this->rebound($abstract); } }docを見るとわかる通り、
$concrete
には、クロージャか文字列が渡されることが想定されている。クロージャなら解決方法となるインスタンスの生成処理、文字列ならFQCNだと思って良い。
bind
の処理の順序としては、まずdropStaleInstances
メソッドで同じキーで登録されているインスタンスとエイリアスをunset
する。Container.php/** * Drop all of the stale instances and aliases. * * @param string $abstract * @return void */ protected function dropStaleInstances($abstract) { unset($this->instances[$abstract], $this->aliases[$abstract]); }もし
$concrete
に何も引数が渡されなかった場合、デフォルトのnull
が使用されることになり、$concrete
には$abstract
の文字列がそのままコピーされる。
(この$abstract
に抽象クラスのFQCNを渡しておきながら$concrete
に何の解決方法も渡さなかった場合、 この処理が走った上でbuild
に処理が移るため、例外が投げられてしまう。)次に、
$concrete
がクラス名の場合、getClosure
メソッドを使ってインスタンスを生成するためのクロージャを作成する。Container.php/** * Get the Closure to be used when building a type. * * @param string $abstract * @param string $concrete * @return \Closure */ protected function getClosure($abstract, $concrete) { return function ($container, $parameters = []) use ($abstract, $concrete) { if ($abstract == $concrete) { return $container->build($concrete); } return $container->resolve( $concrete, $parameters, $raiseEvents = false ); }; }その後、
compact
で配列を作成し、$bindings
プロパティに格納する。
compact
はPHPの標準関数であり、変数名とその値から連想配列を作成する。makeによる解決
解決処理に使われる
make
メソッドは、resolve
メソッドを呼んでその結果を返しているだけ。Container.php/** * Resolve the given type from the container. * * @param string $abstract * @param array $parameters * @return mixed * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function make($abstract, array $parameters = []) { return $this->resolve($abstract, $parameters); }resolveによる抽象クラスの解決処理
前述の通り、抽象クラスの場合は
build
メソッドによるインスタンス化はできない。
そこで解決方法をクロージャとして用意し、resolve
メソッドの内部でそれを取り出し、改めてbuild
に処理を委譲し、インスタンスを返却する。Container.php/** * Resolve the given type from the container. * * @param string $abstract * @param array $parameters * @param bool $raiseEvents * @return mixed * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function resolve($abstract, $parameters = [], $raiseEvents = true) { $abstract = $this->getAlias($abstract); $needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($abstract) ); if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { return $this->instances[$abstract]; } $this->with[] = $parameters; $concrete = $this->getConcrete($abstract); if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } if ($raiseEvents) { $this->fireResolvingCallbacks($abstract, $object); } $this->resolved[$abstract] = true; array_pop($this->with); return $object; }やりすぎなぐらいなんか色々やってますが、ここで重要と思われるのは真ん中辺り以降の処理。
まず$parameters
を$with
プロパティに一旦コピーした後、getConcrete
メソッドで解決方法となるクロージャを取得する。結合が存在しない場合は$abstract
が具象クラスのFQCNであると判断し、そのまま返却する。Container.php/** * Get the concrete type for a given abstract. * * @param string $abstract * @return mixed $concrete */ protected function getConcrete($abstract) { if (! is_null($concrete = $this->getContextualConcrete($abstract))) { return $concrete; } // 結合されたクロージャを取り出す。 if (isset($this->bindings[$abstract])) { return $this->bindings[$abstract]['concrete']; } // bindされていない場合、そのまま $abstract を返す。 return $abstract; }そして、
isBuildable
メソッドを呼び、インスタンス化をbuild
メソッド、make
メソッドのどちらに任せるかを決定する。$concrete
と$abstract
が同じ文字列であるか、$concrete
がクロージャである場合、build
メソッドに処理が移り、インスタンス化が行われる。Container.php/** * Determine if the given concrete is buildable. * * @param mixed $concrete * @param string $abstract * @return bool */ protected function isBuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof Closure; }そしてそのどちらでもなかった場合、
make
メソッドに処理が移り、その中で再度resolve
メソッドが呼ばれる。つまり、「ネストした依存」をすべてbuild
メソッドで解決できるまで、この処理は再帰的に繰り返されることになる。「ネストした依存」の解決の一例を示すものとしてわかりやすいのが下記のテストケース。
関係する抽象クラスの解決方法を用意し、コンストラクタインジェクションを使用しておけば、依存先のクラスも含めてすべて依存を解決した上でインスタンス化してくれることがわかる。tests/Container/ContainerTest.phpuse PHPUnit\Framework\Testcase; class ContainerTest extends TestCase { // ... public function testNestedDependencyResolution() { $container = new Container; $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); $class = $container->make(ContainerNestedDependentStub::class); $this->assertInstanceOf(ContainerDependentStub::class, $class->inner); $this->assertInstanceOf(ContainerImplementationStub::class, $class->inner->impl); } // ... } interface IContainerContractStub { // } class ContainerImplementationStub implements IContainerContractStub { // } class ContainerImplementationStubTwo implements IContainerContractStub { // } class ContainerDependentStub { public $impl; public function __construct(IContainerContractStub $impl) { $this->impl = $impl; } } class ContainerNestedDependentStub { public $inner; public function __construct(ContainerDependentStub $inner) { $this->inner = $inner; } }一旦ここまで。
タグとエイリアス、拡張、コンテキストによる結合などは次回以降(があれば)触れたいと思います。
Laravelコアむずかしい。。。
- 投稿日:2020-03-21T11:24:52+09:00
Serverless Frameworkのアプリにカスタムドメインを付与する
前回作成したLaravelのサーバレスアプリにカスタムドメインでアクセスできるようにします。
ACMで証明書を取得します。Route53経由の方はDNS経由で簡単に取得できます。サブドメインはワイルドカードで申請するのみです。
Lambda関数がEdgeの場合は、us-east-1
(バージニア北部)のACMである必要があります。東京のACMは関係ありません。発行済になったら
serverless-domain-manager
のインストールします。a4e6e25d$ npm install serverless-domain-manager --save-dev
serverless.yml
を編集します。8c313fafserverless.yml+ custom: + customDomain: + domainName: laravel-demo.umihi.co + certificateName: umihi.co + basePath: '' + stage: ${self:provider.stage} + createRoute53Record: true + endpointType: 'edge' + securityPolicy: tls_1_2 provider: name: aws region: ap-northeast-1 runtime: provided plugins: - ./vendor/bref/bref + - serverless-domain-manager最後に
sls create_domain
してsls deploy
しますが、create_domain
実行後は最大40分待つ必要があります。$ sls create_domain Serverless: Custom domain laravel-demo.umihi.co was created. New domains may take up to 40 minutes to be initialized. $ sls deploy Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service laravel-demo.zip file to S3 (14.2 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... .............. Serverless: Stack update finished... Service Information service: laravel-demo stage: dev region: ap-northeast-1 stack: laravel-demo-dev resources: 12 api keys: None endpoints: ANY - https://td3rzowchc.execute-api.ap-northeast-1.amazonaws.com/dev ANY - https://td3rzowchc.execute-api.ap-northeast-1.amazonaws.com/dev/{proxy+} functions: website: laravel-demo-dev-website layers: None Serverless: Created basepath mapping. Serverless Domain Manager Summary Domain Name laravel-demo.umihi.co Distribution Domain Name Target Domain: aaaaabbbbbcccc.cloudfront.net Hosted Zone Id: XXXXYYYYYZZZZZ Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.https://laravel-demo.umihi.co にアクセスできるようになりました。
カスタムドメイン+証明書+サーバレス+Laravelの完成です。Cloudfrontの文字が見えますが、当該IDは、 https://console.aws.amazon.com/cloudfront
には無く、API Gateway配下の https://ap-northeast-1.console.aws.amazon.com/apigateway/main/publish/domain-names
で確認できます。
- 投稿日:2020-03-21T09:30:15+09:00
XAMPP なしで、Windows の Visual Studio Code を使って PHP デバッグしてみる
XAMPPを入れれば簡単に実現できますが、タイトルとおり「Webサーバなしで」あえてローカル PHP だけインストールして Visual Studio Code(以下 VSCode)で PHP を実行&デバッグしてみます。※XAMPPが嫌いな訳ではない
お約束の微妙にはまる点があったので自分メモ用に残しておきます。
動作環境
- Windows 8/10
- Visual Studio Code 1.43.1 ※インストール済みであること
- PHP 7.4.4 (VC15 x64 Thread Safe / php-7.4.4-Win32-vc15-x64.zip)
PHP のインストール
https://windows.php.net/download#php-7.4 から Thread Safe 版をダウンロードして任意のディレクトリに展開しておく。(ここでは C:\php7 にインストールしてみてます)
Windowsの環境変数に
C:\php7
を追加して黒い画面でphp -v
を叩いてみる。と…下記が表示されなければ、いいのだが…
PHP Warning: 'vcruntime140.dll' 14.0 is not compatible with this PHP build linked with 14.16 in Unknown on line 0表示された場合は、「Visual Studio 2019 の Microsoft Visual C++ 再頒布可能パッケージ」をインストールすると解消します。
ダウンロード先は、ここ からですが判りづらい。下までスクロールして「その他のツールとフレームワーク」をクリックすると、ランタイムライブラリのダウンロードが項目あります。
PHPデバッグツール「XDebug」のインストール
PHPのバージョンに合わせた形で XDebugがあるようですのでそのインストール手順。
手順1
C:\PHP7
の下にあるphp.ini-production
をphp.ini
にコピーする。手順2
黒い画面で以下のコマンドを実行する。クリップボードに結果がコピーされます。C:\> php -i | cliphttps://xdebug.org/wizard サイトに結果を貼り付け "Analyse my phpinfo output" ボタンを押下する。
上記の Instructions に従い xdebug の DLL をダウンロードし C:/php7/ext 以下に配置する。
手順3
php.ini
の先頭に下記をコピペする。上記の Instructions で指示されているzend_extension
パスは指定されたとおりに記述すると間違っているので、DLL を配置したパスをちゃんと記述する。zend_extension = C:\php7\ext\php_xdebug-2.9.3-7.4-vc15-x86_64.dll xdebug.remote_enable = 1 xdebug.remote_autostart = 1 xdebug.remote_host = 127.0.0.1 xdebug.remote_port = 9000黒い窓で
php -v
と実行してみて下記の Warning(warn なのに Failed …) が表示されていなければOK。PHP Warning: Failed to load C:/php/ext/php_xdebug-2.9.3-7.4-vc15-x86_64.dll, 指定されたファイルが見つかりません。VSCode拡張機能 PHP Debug のインストール
左バーの拡張機能で 「PHP Debug」、「PHP IntelliSense」をインストールする。
デバックしてみる
適当な phpプログラムを表示し。左バーの実行を押下し "To customize Run and Debug create a launch.json" のリンクを踏む。プルダウンから「PHP」を選択する。
launch.json
は、以下の様に1行追加する。"name": "Listen for XDebug" は消しても問題ない。{ "name": "Launch currently open script", "type": "php", "request": "launch", "program": "${file}", "cwd": "${fileDirname}", "port": 9000, "runtimeExecutable": "C:\\php7\\php.exe" }デバッガーを起動しつつブレイクポイントを設定し "Listen for XDebug"を実行する。
うまくいった^^参考サイト
- 投稿日:2020-03-21T09:10:24+09:00
Laravelのサーバレス用ライブラリbrefを使い、lambdaでhello world
プロジェクトを作ってから少し手を加えるだけで、Laravelのサーバレス化ができました。
AWS上のデプロイはServerless Frameworkが全てやってくれます。composer create-project --prefer-dist laravel/laravel laravel-demo #プロジェクト作成 cd laravel-demo composer require bref/bref #肝のbrefインストール以下の編集を加えます。b508b15
.env- SESSION_DRIVER=file + SESSION_DRIVER=array + VIEW_COMPILED_PATH=/tmp/storage/framework/viewsconfig/logging.php'stack' => [ 'driver' => 'stack', - 'channels' => ['single'], + 'channels' => ['stderr'], 'ignore_exceptions' => false, ],/app/Providers/AppServiceProvider.phppublic function boot() { - // + if (!is_dir(config('view.compiled'))) { + mkdir(config('view.compiled'), 0755, true); + } } }最後に
serverless.yml
を追加します。007fb31serverless.ymlservice: laravel-demo provider: name: aws region: ap-northeast-1 runtime: provided plugins: - ./vendor/bref/bref package: exclude: - node_modules/** - public/storage - storage/** - tests/** functions: website: handler: public/index.php timeout: 28 # in seconds (API Gateway has a timeout of 29 seconds) layers: - ${bref:layer.php-73-fpm} events: - http: 'ANY /' - http: 'ANY /{proxy+}' # artisan: # handler: artisan # timeout: 120 # in seconds # layers: # - ${bref:layer.php-73} # PHP # - ${bref:layer.console} # The "console" layer本家の
artisan
コマンド用関数ですが、私はローカルでしか実行しないのでコメントアウトしています。デプロイコマンドは
php artisan config:clear
とsls deploy
のセットです。
以下のアウトプットになりました。$ php artisan config:clear Configuration cache cleared! $ sls deploy Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress... ........ Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service laravel-demo.zip file to S3 (14.19 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ................................. Serverless: Stack update finished... Service Information service: laravel-demo stage: dev region: ap-northeast-1 stack: laravel-demo-dev resources: 12 api keys: None endpoints: ANY - https://td3rzowchc.execute-api.ap-northeast-1.amazonaws.com/dev ANY - https://td3rzowchc.execute-api.ap-northeast-1.amazonaws.com/dev/{proxy+} functions: website: laravel-demo-dev-website layers: None Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.さっそく https://td3rzowchc.execute-api.ap-northeast-1.amazonaws.com/dev にアクセスしてみましょう。
以上です。以下の課題に対しても記事投稿予定です。
- URLにあるステージのパスが外せないことによる不具合が多いので、カスタムドメインを導入する
- public配下が参照されず、jsとcssが参照できない。
- slsコマンドと同期して.envを環境毎に使い分けたい。
- セッション含めDB接続したいがサーバレスはコネクションプール問題がある。
- 投稿日:2020-03-21T01:38:34+09:00
Laravelでスコープ
定義方法
scope名は必ず最初にscopeを名前の初めに付ける必要がある.
・scopeUserName
・scopeBookSerialNo
上記のようにスコープ名をつける。public function scope+名前($query, 引数) { # 処理内容 return 絞り込んだビルダ(検索条件) }ローカルスコープ
定義したメソッドを呼び出す場合に実行することができるスコープ。
逆に定義していれば自動的にスコープメソッドが実行されるものはグローバルスコープ。Person.php# scopeの定義 public function scopeNameEqual($query, $str) { return $query->where('name', $str); }PersonController.phppublic function search(Request $request){ $input_val = $request->input; $item = Person::NameEqual($input_val)->first(); $params = ['input' => $input_val, 'id' => $request->id, 'item' => $item]; return view('person.find', $params); }スコープを複数繋げて利用する
2種類のスコープを定義
Model.php// $min(変数の値)以上のageを検索するスコープ public function scopeAgeGreaterThan($query, $min) { return $query->where('age', '>=', $min); } // $max(変数の値)以下のageを検索するスコープ public function scopeAgeLessThan($query, $max) { return $query->where('age', '<=', $max); }下記サンプルのように条件をつなげて使う
例:ageが "リクエストで受け取った値" 〜 "値+10" の間のデータを取得するスコープ
()Controller.php$min = $request->input * 1; $max = $min + 10; $items = Person::ageGreaterThan($min)->ageLessThan($max)->get();グローバルスコープ
明示的にメソッドを呼び出さなくても自動的にスコープの検索ありきで検索が実行される
1.use Illuminate\Database\Eloquent\Builder; ←Builder使えるようにする
2.bootメソッドでモデルの初期化を行う
3.bootメソッド内でaddGlobalScopeクロージャを書く
4.addGlobalScopeクロージャの中でスコープの処理を定義していくこうすることでモデルクラスでbootメソッドがオーバーライドされる
Model.phpuse Illuminate\Database\Eloquent\Builder; class Person extends Model { // グローバルスコープ定義 // protected static function boot() { parent::boot(); static::addGlobalScope('age', function(Builder $builder){ $builder->where('age', '>', 20); }); } ... ... ... }Scopeクラス作成(グローバルスコープ)
複数のモデルや、その他のプロジェクトなどでも利用されるような汎用性の高い処理は
Scopeクラス
を使うと便利.Scopeクラスの作成
1.Scopesディレクトリ(名前は任意)を作成。
2.Scopeクラスの作成(仮でScopePersonというクラスにします)常に20歳より年上のみを取得するスコープを作成するため下記コード記載
Scopes/ScopePerson.php<?php namespace App\Scope; use Illuminate\Database\Eloquent\Scope; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; class ScopePerson implements Scope { public function apply(Builder $builder, Model $model) { $builder->where('age', '>', 20); } }Model側でグローバルスコープを定義
・useしてScopePersonを参照できるようにしたらstatic::addGlobalScope(New ScopePerson)
してあげるだけModel.phpuse App\Scope\ScopePerson; // グローバルスコープ定義 // protected static function boot() { parent::boot(); static::addGlobalScope(New ScopePerson); }こうすることで
Person::all();
等で複数データを取得した場合に自動的にageが20以下のものはフィルターされる
- 投稿日:2020-03-21T00:58:43+09:00
Wordpressのカスタマイザーで設定できるオプションタイプとサンプルコードまとめ
カスタマイザーで使えるオプションのタイプ一覧とサンプルコードまとめ。
カスタマイザーの基本構造
カスタマイザーはfunctions.phpに書く。
セクション、設定、コントロール(フォーム)の3つを定義する必要がある。
セクションはグループなので1つでもOK。設定とコントロールは対になってるのでオプションごとに両方とも必要。
function mytheme_customize_register( $wp_customize ) { // セクション(グループ)を作成 $wp_customize->add_section( 'your_section_id' , array( 'title' => __( 'Theme Color', 'theme_name' ), 'priority' => 21, ) ); // データベースに新しい設定項目を登録 $wp_customize->add_setting( 'your_setting_id' , array( 'default' => 'Dark', 'transport' => 'refresh', ) ); // 管理画面に表示するコントローラ(フォーム)を登録 $wp_customize->add_control( new WP_Customize_Control( $wp_customize, 'your_setting_id', array( 'label' => __( 'Dark or light theme version?', 'theme_name' ), 'section' => 'your_section_id', 'settings' => 'your_setting_id', 'type' => 'radio', 'choices' => array( 'dark' => __( 'Dark' ), 'light' => __( 'Light' ) ) ) ) ); } add_action( 'customize_register', 'corp_customize_register' );カスタマイザーで使えるオプションのタイプ
タイプ一覧。
- テキスト
- テキストエリア
- 日付
- 番号
- セレクト
- チェックボックス
- ラジオボタン
- 画像アップロード
- カラー設定
テキスト、テキストエリア
テキスト入力ができるオプションを追加。
テキストエリアの場合は
text
の部分をtextarea
に変更する。$wp_customize->add_setting( 'text_setting_id', array( 'capability' => 'edit_theme_options', 'default' => 'テキスト', 'sanitize_callback' => 'sanitize_text_field', ) ); $wp_customize->add_control( 'text_setting_id', array( 'label' => __( 'Custom Text' ), 'description' => __( 'This is a custom text box.' ), 'section' => 'your_section_id', 'type' => 'text', // textareaでテキストエリアに変更可能 ) );日付
カレンダーを追加。
$wp_customize->add_setting( 'date_setting_id', array( 'capability' => 'edit_theme_options', 'sanitize_callback' => 'my_sanitize_date', ) ); $wp_customize->add_control( 'date_setting_id', array( 'label' => __( 'Custom Date' ), 'description' => __( 'This is a custom date control.' ), 'section' => 'your_section_id', 'type' => 'date', 'input_attrs' => array( 'placeholder' => __( 'mm/dd/yyyy' ), ), ) ); function my_sanitize_date( $input ) { $date = new DateTime( $input ); return $date->format('m-d-Y'); }番号
番号フォームを追加。
$wp_customize->add_setting( 'number_setting_id', array( 'capability' => 'edit_theme_options', 'sanitize_callback' => 'my_sanitize_number', 'default' => 1, ) ); $wp_customize->add_control( 'number_setting_id', array( 'label' => __( 'Custom Number' ), 'description' => __( 'This is a custom number.' ), 'section' => 'your_section_id', 'type' => 'number', ) ); function my_sanitize_number( $number, $setting ) { $number = absint( $number ); return ( $number ? $number : $setting->default ); }セレクト
セレクトフォームを追加。
$wp_customize->add_setting( 'select_setting_id', array( 'capability' => 'edit_theme_options', 'sanitize_callback' => 'my_sanitize_select', 'default' => 'value1', ) ); $wp_customize->add_control( 'select_setting_id', array( 'label' => __( 'Custom Select Option' ), 'description' => __( 'This is a custom select option.' ), 'section' => 'your_section_id', 'type' => 'select', 'choices' => array( 'value1' => __( 'Value 1' ), 'value2' => __( 'Value 2' ), 'value3' => __( 'Value 3' ), ), ) ); function my_sanitize_select( $input, $setting ) { $input = sanitize_key( $input ); $choices = $setting->manager->get_control( $setting->id )->choices; return ( array_key_exists( $input, $choices ) ? $input : $setting->default ); }チェックボックス
チェックボックスを追加。
$wp_customize->add_setting( 'checkbox_setting_id', array( 'capability' => 'edit_theme_options', 'sanitize_callback' => 'my_sanitize_checkbox', ) ); $wp_customize->add_control( 'checkbox_setting_id', array( 'label' => __( 'Custom Checkbox' ), 'description' => __( 'This is a custom checkbox input.' ), 'section' => 'your_section_id', 'type' => 'checkbox', ) ); function my_sanitize_checkbox( $checked ) { // Boolean check. return ( ( isset( $checked ) && true == $checked ) ? true : false ); }ラジオボタン
ラジオボタンを追加。
$wp_customize->add_setting( 'radio_setting_id', array( 'capability' => 'edit_theme_options', 'default' => 'blue', 'sanitize_callback' => 'my_sanitize_radio', ) ); $wp_customize->add_control( 'radio_setting_id', array( 'label' => __( 'Custom Radio Selection' ), 'description' => __( 'This is a custom radio input.' ), 'section' => 'your_section_id', 'type' => 'radio', 'choices' => array( 'red' => __( 'Red' ), 'blue' => __( 'Blue' ), 'green' => __( 'Green' ), ), ) ); function my_sanitize_radio( $input, $setting ){ $input = sanitize_key($input); $choices = $setting->manager->get_control( $setting->id )->choices; return ( array_key_exists( $input, $choices ) ? $input : $setting->default ); }画像アップロード
画像アップロードボタンを追加。
$wp_customize->add_setting( 'media_setting_id', array( 'sanitize_callback' => 'absint', 'validate_callback' => 'my_sanitize_file', ) ); $wp_customize->add_control( new WP_Customize_Media_Control( $wp_customize, 'media_setting_id', array( 'label' => __( 'Custom Core Media Setting' ), 'description' => __( 'This is a Image Upload' ), 'section' => 'your_section_id', 'mime_type' => 'image', ) ) ); function my_sanitize_file( $file, $setting ) { //allowed file types $mimes = array( 'jpg|jpeg|jpe' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png' ); //check file type from file name $file_ext = wp_check_filetype( $file, $mimes ); //if file has a valid mime type return it, otherwise return default return ( $file_ext['ext'] ? $file : $setting->default ); }カラー設定
カラーの設定オプションを追加。
$wp_customize->add_setting( 'color_setting_id', array( 'sanitize_callback' => 'sanitize_hex_color', ) ); $wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, 'color_setting_id', array( 'label' => __( 'Core Color Setting' ), 'description' => __( 'Select a color for something' ), 'section' => 'your_section_id', ) ) );
- 投稿日:2020-03-21T00:33:58+09:00
Laravel ベースレイアウトを使って表示するページのheaderやfooterのデザインを統一しよう
目的
- ベースレイアウトの定義と継承を行いデザインの統一化やコーディングの簡略化の方法を学んだので忘れない様にまとめる
- 主に下記の本に記載されていたことを筆者なりに解釈してまとめる(筆者の文章が稚拙なため、深く勉強したい方は書籍を購入した方が良いかも知れない)
ベースレイアウトとは?
- 文字通りビューファイルのベースとなるレイアウトである。
- アプリケーション内で共通のデザインや共通のhtmlのhead情報などを予め記載しておくことができる。
- ベースレイアウトをビューファイルで継承すれば簡単にデザインの統一ができる。
わかった気になれる考え方(認識違ったらすいません)
ベースレイアウトはスーパーで売っているお弁当を入れる容器をイメージしていただきたい。
ちょっとお堅い考え方(認識違ったらすいません)
- ベースレイアウトでは表示するページの大枠の部分を定義する。
- 共通するhtmlのhead情報、headerの場所、mainの場所、footerの場所などなど
- header、main、footerなどの場所はそれぞれセクションと呼ばれる単位で定義する。
- headerセクション、mainセクション、footerセクションを定義した場合、そのベースレイアウト継承した別のビューファイルで各セクションに入れるコードを指定することができる。(詳しい方法は後述する。)
- また先に記載したセクションだけでなく任意の名前のセクションを作成することができる。
headerとfooterのデザインを共通にするための書き方の例
- ※フォルダ名、ファイル名は時に決まりはないため、例と完全一致してなくても良いが、お試しで実施する場合下記の名前でベースレイアウトを記載するファイルとそれを格納するフォルダを作成することをお勧めする。
- laravelのviewファイル内にlayoutというフォルダを作成する。
- layoutフォルダ内にapplication.blade.phpというファイルを作成する。
- application.blade.phpファイルがベースレイアウトを記載するファイルである。
application.blade.phpに下記の記載を行う。
application.blade.php<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <header> <h1>これはheaderです</h1> </header> <main> <div> @yield('content') </div> <footer> <h1>これはfooterです</h1> </footer> </main> </body> </html>お好きな名前で新規にビューファイルを作り、そのビューをブラウザで表示できる最低限の処理をルーティングとコントローラに記載する。
新規のビューファイルに下記の記載を行う。
@extends('layout.application') @section('content') <p>これはベースレイアウトで作成したコンテンツです。</p> @endsection新規のビューファイルを表示リンクをブラウザで開き、表示を確認する。
簡単な解説
@yield('セクション名')
- ここに後述するセクション宣言時に記載したコードが入ってブラウザで表示される。
- セッションを呼び出している。
- お弁当の容器でいうところの具材が入る区画である。
@extends('ベースレイアウト格納フォルダ名.ベースレイアウトファイル名')
- これは継承の設定である。
- ビューファイルに記載する。ベースレイアウトのファイルには記載しない。
@section('セクション名')
@endsection
- セクションの宣言である。
- セクション内に記載したいコードを
@section('セクション名')
から@endsection
の間に記載する。- お弁当の容器でいうところの具材の決定である。
上記をまとめると
- セクションの宣言は
@section('セクション名')
と@endsection
の間にセクションに記載したいコードを書く。@yield('セクション名')
の部分に@section('セクション名')
と@endsection
の間に書かれたコードが記載されて表示される。- ベースレイアウトとページ表示用のビューファイルは
@extends('ベースレイアウト格納フォルダ名.ベースレイアウトファイル名')
を用いて継承を行うことで初めてセッション宣言とセッションの呼び出しは使用できない。- 今回はheaderとfooterのデザイン統一を目的としておりベースレイアウトにセッションの呼び出しを記載し、ビューファイルにセッションの宣言を記載したが、目的が異なる場合ベースレイアウトでのセッション宣言、ビューファイルでのセッションの呼び出しを行うことももちろんあり得る。
すいません
- 筆者なりに考えてわかりやすく伝えようとした結果、とてもわかりにくい説明になってしまったことをお詫びします。
- 記事内のリンクはアフィリエイトリンクではないのでご安心ください。