20200321のPHPに関する記事は15件です。

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 21st

mktime関数

日付を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); // 95178240

time関数

現在の Unix タイムスタンプを返す。マイクロ秒の単位まで返したいときには、microtime()を利用する。

利用例

echo time(); // 1584796017

$now = time();
echo date("F j, Y, g:i a", $now); // March 21, 2020, 1:08 pm

getdate関数

日付/時刻情報を取得する

説明

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
)-->
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LravelのDBテーブル作成&データ投入

お約束事

本記事で発信される情報は、正確性、完全性、有用性、その他の事項について一切責任を負いかねます。自己判断にてご活用ください。

全体の流れ

  • Migrationを行いテーブルを作成する

    1. migrationコマンドを実行するための空ファイルの作成
    2. 作成されたmigrationファイルをテーブル構造通りに記述する
    3. migrateコマンドで実際にテーブルを作成する
    4. 型変更、カラム追加・削除等
  • Sheederを用いてテーブルデータを投入する

    1. seedを実行するための空ファイルの作成
    2. 作成されたseedファイルを投入したいデータに合わせて記述する
    3. 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.

※数百件レベルのデータなら秒で終わるので負荷とかは検討していません。

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

【fullcalendar】Laravelとfullcalendar(v4)を使って予約管理システムを作った話【Laravel】

仮想案件
家庭教師予約システムをつくる

仕様概要
・家庭教師派遣会社は先生(学生バイト達)をAdmin管理画面からスケジューリング
・会員(生徒)は任意の先生を管理画面から予約
・派遣会社の画面と会員の画面はそれぞれ別の認証経路(マルチAuth)
・UIはGoogleカレンダー風を希望

前提
・fullcalendarメインの記事になります
・Laravelの基本を理解している方向けです
・非同期通信の基本も理解している方向けです
・データベース設計の基本も理解している方向けです
要はfullcalendarにポイントを置いてます。

目次

  1. 完成イメージ
  2. LaravelのマルチAuth対応
  3. 管理画面の作成
  4. fullcalendarの組み込み
  5. ビジネスロジックの実装
  6. 補足

1.完成イメージ

家庭教師派遣会社が使用するAdmin管理画面(週表示)
カレンダー左横の先生の箱をカレンダーにドロップしてイベントを作成
スクリーンショット_2020-03-21_11_08_44.png

会員(生徒)が使用する予約画面(週表示)
任意のイベント(先生)をクリックして予約処理をする
スクリーンショット_2020-03-21_11_36_20.png

会員(生徒)が使用する予約画面(月表示)
スクリーンショット_2020-03-21_11_40_35.png

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-eventdiv要素(カレンダー左に列挙されてるドラッグ&ドロップ可能要素)には今回先生の名前が入るので、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.php
document.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.php
document.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.php
var dt = new Date();
info.event.setExtendedProp('identifier',dt.getTime());

最後に

ここで紹介したのはほんの一部です。
いろいろできるので公式のぞいてみてください。

fullcalendar公式
External Event Dragging

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

echoとprintの違い

PHPのechoprintはどちらも関数ではなく言語構造です。どちらも文字列を出力バッファに書き出すという機能では同じですが、使われかたが異なります。

echo 1, 2, 3; // ← syntax error ではない
print 1, 2, 3; // ← これは syntax error

echo print 1; // ← syntax error ではない
print echo 1; // ← これは syntax error

形式的に言うと

  • echoprintはどちらも出力バッファに書き込む機能です
    • まれに「標準出力する」と言及されますが、それは間違いです
    • fwrite(STDOUT, $str)の実行結果とは一致しないということです
  • echoは文ですがprintは式です
  • echoは複数の引数をとりますが、printは1つの引数をとります
  • 便宜上関数マニュアルに掲載されているが関数ではありません

どれを使えばいいの?

好みで何を使ってもいいと思うのですが、printを使うメリットは特にないのでechoを薦めます。

echo $string;

または

echo $str1, $str2, PHP_EOL;

この形式だけを使えばいいと思います。

また、 echoprintはどちらも関数ではないので echo($v)print($v)のような関数呼び出し風の記述は誤解を呼ぶので使わない方が良いと思います。

echo

https://www.php.net/echo

echo — 1 つ以上の文字列を出力する

print との主な違いは、 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

https://www.php.net/print

print — 文字列を出力する

echoとの主な違いは、printが単一の引数のみ受け付け、常に 1 を返すことです。

この説明からはわかりにくいですが、printは式であり、式の中に書くことができるということです。

どうしてわざわざ、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は関数呼び出しではなく言語構造なので、print(1)と書いても(print 1)と書いても同じことです。print(1, 2)と書いてしまうと関数呼び出しのように錯覚させてしまいますが、これは関数呼び出し式ではないため、純粋にsyntax errorです。

printf()

せっかくなのでprintf()にも言及しておきましょう。echoprintは言語構造でしたが、printf()はPHPの組み込み関数です。

https://www.php.net/printf

printf — フォーマット済みの文字列を出力する

これも出力バッファに文字列を書き出すという意味では共通していますが、意味が異なります。echoprintが単に文字列を出力するだけのものであるのに対して、printf()はフォーマット文字列を整形する、一種のテンプレートエンジンと呼べるものです。

printf('%d + %d = %d', $a, $b, $a + $b);

printf()は高機能なので活用すると便利ですが、echoprintとは違う使いかたを意識する必要があります。

// 安全なコード
echo "こんにちは {$name} さん! ({$num}回め)";

// 危険なコード
printf("こんにちは {$name} さん! (%d回め)", $num);

後者のコードでは$name%が含まれると実行時エラーやテンプレートの位置をずらされるといった問題が起きえます。sprintf()を使って文字を組み立てる場合などもバグや脆弱性の原因となりうることがあるでしょう。

printf("こんにちは %s さん! (%d回め)", $name, $num);

printf()sprintf()などの第一引数には変数を直接展開せず、printf()%sなどを使ってください。

オペコードとの関係

PHP5時代はオペコードレベルでPRINTECHOは別の命令でしたが、PHP7ではECHO命令に統合されており最適化も施されるので特に違いは生じないはずです。

2015年9月に出版されたPHPはどのように動くのか ~PHPコアから読み解く仕組みと定石ではprintよりもechoの方がパフォーマンスに優れていると読み取れる記述があるのですが、実際には2015年12月にはPHP 7.0がリリースされており、実態にそぐわない記述になってしまいました。

読者への課題

PHP-Parserを使って、以下のようなソースコードを生成してみましょう。

<?php

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

Fuelを使ってパラメータ付きPOSTリクエストを送信した

なんとかHTTPリクエストをAndroidのエミュレーターからローカルホストのLaravelサーバーに送信することができたので投稿します。

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

app/build.gradleにインストールするライブラリを書いていきます。

app/build.gradle
dependencies {
    // 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.kt
class SampleRequestFormat(val kekey: String)

kekeyという名前のキーを定義しました。

Androidの画面作成

画面にボタンが一つ配置されているだけのものです。

image.png

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.kt
import 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.php
Route::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 Android  

Androidのログ↓

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リクエストが送られています!

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

本日の学習内容part2

参考記事

https://phpjp.com/sha1.php

パスワードの暗号化(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();

感想

学習した内容を上手く文章に出来なくててもどかしい(笑) これからそのスキルも磨いていきたい。

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

配列で受け取った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'
  );
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CircleCi2.0でphpunit実行時に Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate... が出たときの対処法

やってもダメだったこと

php.ini
memory_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=512M

phpunitの実行オプションに-d memory_limit=512Mをつけることで解決しました。

なかなか解決策が見つからなくて、かなりハマってしまいました(汗)
この記事が誰かのお役に立てたら何よりです。

参考にさせていただいた記事

https://qiita.com/pinekta/items/4ece0c88402610f874d0
↓こちらに解決策が載ってました。
https://discuss.circleci.com/t/php-memory-size/7602

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

Laravel ソースコードリーディング サービスコンテナ編 part.1 - bindとmake、buildとresolve -

いい加減サービスコンテナをよく知らずに使うのはよくないな〜ということでLaravelコアのコードを読みつつ、記事を書いて投げてみました。同じようなLaravel初心者の方の参考になれば幸い...

ただ、僕もまだまだ未熟ですので、意図の読み違いなどは普通にあり得ると思います。間違いなどありましたらご指摘くだされば幸いです:pray:

基礎知識: サービスコンテナ

DIコンテナとも。Dependency Injection パターンと併用して、依存性を1箇所に集める。
Laravelアプリケーション内における様々なインスタンスの生成を担う。Laravelでは文字通り、これがないとアプリケーションが始まらない。Laravelのコアと言って良い。
FQCNは Illuminate\Foundation\Applicationクラスで、Laravel中ではよく$appというプロパティとして現れる。
Illuminate\Foundation\ApplicationクラスはIlluminate\Container\Containerクラスを継承しており、コンテナの結合処理などを担うのはこちらのクラスなので、最初はこっちを見ることになる。

用語

結合(bind)

キーと解決処理をペアでコンテナに登録すること。
結合にはbindsingletoninstanceなどのメソッドを使う。

呼び出し時に使用されるキーには大抵FQCNが使用されるが、極端な話文字列であればなんでも良い。ファサードの場合はわかりやすく短い文字列が使われやすい(DBファサードならdbとか)。

具象クラスの場合、Laravelは勝手にそのクラスを探し出しインスタンス化してくれるので、結合処理を書く必要性は特にない(はず)。言い換えると、後述の処理内容を見ればわかりますが、コンテナに結合されていないキーで問い合わせた場合、そのキーと一致するクラスを探し、インスタンスを生成しようとする処理が走ることになる。

結合処理は大抵、サービスプロバイダ内で行われる。

解決(resolve)

結合されたインスタンス化の方法を元に、インスタンスを生成すること。
明示的な解決には基本的にmakeメソッドを使用する。

実際の処理

doc以外のコメントは省略しています。

buildによる具象クラスの解決処理

順序的にはまずbuildメソッドの、実際に与えられたクラス名からインスタンスを生成する役割を理解しておくと良さそう。ReflectionClassReflectionParameterに関してはわからない方は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.php
use 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コアむずかしい。。。

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

Serverless Frameworkのアプリにカスタムドメインを付与する

前回作成したLaravelのサーバレスアプリにカスタムドメインでアクセスできるようにします。
ACMで証明書を取得します。Route53経由の方はDNS経由で簡単に取得できます。サブドメインはワイルドカードで申請するのみです。
Lambda関数がEdgeの場合は、us-east-1(バージニア北部)のACMである必要があります。東京のACMは関係ありません。

スクリーンショット 2020-03-21 10.54.52.png

発行済になったらserverless-domain-managerのインストールします。a4e6e25d

$ npm install serverless-domain-manager --save-dev

serverless.ymlを編集します。8c313faf

serverless.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
で確認できます。

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

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-productionphp.ini にコピーする。

手順2
黒い画面で以下のコマンドを実行する。クリップボードに結果がコピーされます。

C:\> php -i | clip

https://xdebug.org/wizard サイトに結果を貼り付け "Analyse my phpinfo output" ボタンを押下する。

vscode_php_1.png

上記の 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」を選択する。

vscode_php_2.png

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"を実行する。
うまくいった^^

vscode_php_3.png

参考サイト

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

Laravelのサーバレス用ライブラリbrefを使い、lambdaでhello world

プロジェクトを作ってから少し手を加えるだけで、Laravelのサーバレス化ができました。
AWS上のデプロイはServerless Frameworkが全てやってくれます。

本家のドキュメントはこちら
Githubはこちら

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/views
config/logging.php
         'stack' => [
             'driver' => 'stack',
-            'channels' => ['single'],
+            'channels' => ['stderr'],
             'ignore_exceptions' => false,
         ],
/app/Providers/AppServiceProvider.php
     public function boot()
     {
-        //
+        if (!is_dir(config('view.compiled'))) {
+            mkdir(config('view.compiled'), 0755, true);
+        }
     }
 }

最後にserverless.ymlを追加します。007fb31

serverless.yml
service: 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:clearsls 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 にアクセスしてみましょう。
スクリーンショット 2020-03-21 8.50.03.png

以上です。以下の課題に対しても記事投稿予定です。

  1. URLにあるステージのパスが外せないことによる不具合が多いので、カスタムドメインを導入する
  2. public配下が参照されず、jsとcssが参照できない。
  3. slsコマンドと同期して.envを環境毎に使い分けたい。
  4. セッション含めDB接続したいがサーバレスはコネクションプール問題がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelでスコープ

定義方法

scope名は必ず最初にscopeを名前の初めに付ける必要がある.
・scopeUserName
・scopeBookSerialNo
上記のようにスコープ名をつける。

public function scope+名前($query, 引数)
    {
      # 処理内容
      return 絞り込んだビルダ(検索条件)
    }

ローカルスコープ

定義したメソッドを呼び出す場合に実行することができるスコープ。
逆に定義していれば自動的にスコープメソッドが実行されるものはグローバルスコープ。

Person.php
# scopeの定義
  public function scopeNameEqual($query, $str)
  {
    return $query->where('name', $str);
  }
PersonController.php
  public 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.php
use 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.php
use App\Scope\ScopePerson;

  // グローバルスコープ定義 //
  protected static function boot()
  {
    parent::boot();
    static::addGlobalScope(New ScopePerson);
  }

こうすることで
Person::all();等で複数データを取得した場合に自動的にageが20以下のものはフィルターされる

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

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' );

カスタマイザーで使えるオプションのタイプ

タイプ一覧。

  • テキスト
  • テキストエリア
  • 日付
  • 番号
  • セレクト
  • チェックボックス
  • ラジオボタン
  • 画像アップロード
  • カラー設定

custom-type1.png

テキスト、テキストエリア

テキスト入力ができるオプションを追加。

テキストエリアの場合は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',
) ) );
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel ベースレイアウトを使って表示するページのheaderやfooterのデザインを統一しよう

目的

  • ベースレイアウトの定義と継承を行いデザインの統一化やコーディングの簡略化の方法を学んだので忘れない様にまとめる
  • 主に下記の本に記載されていたことを筆者なりに解釈してまとめる(筆者の文章が稚拙なため、深く勉強したい方は書籍を購入した方が良いかも知れない)

ベースレイアウトとは?

  • 文字通りビューファイルのベースとなるレイアウトである。
  • アプリケーション内で共通のデザインや共通のhtmlのhead情報などを予め記載しておくことができる。
  • ベースレイアウトをビューファイルで継承すれば簡単にデザインの統一ができる。

わかった気になれる考え方(認識違ったらすいません)

  • ベースレイアウトはスーパーで売っているお弁当を入れる容器をイメージしていただきたい。

    • 下記に筆者がイメージする商品画像を記載する。

      スクリーンショット 2020-03-20 1.17.11.png

    • 表示されるページのイメージが、具材が詰め込まれ、店舗に陳列されているお弁当と考えていただきたい。

    • 容器のない状態から、同じデザインで幕の内弁当やのり弁当の具材を配置して似たデザインにすることは難しい。

    • なので先ほどの画像の様な「区切り」のついた容器を作り、それに沿って具材を入れてゆくことにより、幕の内弁当でも、のり弁当でも、焼肉弁当でもご飯の比率やおしんこの量を一定に簡単に保つことがベースレイアウトの考え方である。

ちょっとお堅い考え方(認識違ったらすいません)

  • ベースレイアウトでは表示するページの大枠の部分を定義する。
    • 共通するhtmlのhead情報、headerの場所、mainの場所、footerの場所などなど
  • header、main、footerなどの場所はそれぞれセクションと呼ばれる単位で定義する。
  • headerセクション、mainセクション、footerセクションを定義した場合、そのベースレイアウト継承した別のビューファイルで各セクションに入れるコードを指定することができる。(詳しい方法は後述する。)
  • また先に記載したセクションだけでなく任意の名前のセクションを作成することができる。

headerとfooterのデザインを共通にするための書き方の例

  • ※フォルダ名、ファイル名は時に決まりはないため、例と完全一致してなくても良いが、お試しで実施する場合下記の名前でベースレイアウトを記載するファイルとそれを格納するフォルダを作成することをお勧めする。
  1. laravelのviewファイル内にlayoutというフォルダを作成する。
  2. layoutフォルダ内にapplication.blade.phpというファイルを作成する。
  3. application.blade.phpファイルがベースレイアウトを記載するファイルである。
  4. 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>
    
  5. お好きな名前で新規にビューファイルを作り、そのビューをブラウザで表示できる最低限の処理をルーティングとコントローラに記載する。

  6. 新規のビューファイルに下記の記載を行う。

    @extends('layout.application')
    
    @section('content')
        <p>これはベースレイアウトで作成したコンテンツです</p>
    @endsection
    
  7. 新規のビューファイルを表示リンクをブラウザで開き、表示を確認する。

簡単な解説

@yield('セクション名')

  • ここに後述するセクション宣言時に記載したコードが入ってブラウザで表示される。
  • セッションを呼び出している。
  • お弁当の容器でいうところの具材が入る区画である。

@extends('ベースレイアウト格納フォルダ名.ベースレイアウトファイル名')

  • これは継承の設定である。
  • ビューファイルに記載する。ベースレイアウトのファイルには記載しない。

@section('セクション名') @endsection

  • セクションの宣言である。
  • セクション内に記載したいコードを@section('セクション名')から@endsectionの間に記載する。
  • お弁当の容器でいうところの具材の決定である。

上記をまとめると

  • セクションの宣言は@section('セクション名')@endsectionの間にセクションに記載したいコードを書く。
  • @yield('セクション名')の部分に@section('セクション名')@endsectionの間に書かれたコードが記載されて表示される。
  • ベースレイアウトとページ表示用のビューファイルは@extends('ベースレイアウト格納フォルダ名.ベースレイアウトファイル名')を用いて継承を行うことで初めてセッション宣言とセッションの呼び出しは使用できない。
  • 今回はheaderとfooterのデザイン統一を目的としておりベースレイアウトにセッションの呼び出しを記載し、ビューファイルにセッションの宣言を記載したが、目的が異なる場合ベースレイアウトでのセッション宣言、ビューファイルでのセッションの呼び出しを行うことももちろんあり得る。

すいません

  • 筆者なりに考えてわかりやすく伝えようとした結果、とてもわかりにくい説明になってしまったことをお詫びします。
  • 記事内のリンクはアフィリエイトリンクではないのでご安心ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む