20210308のPHPに関する記事は14件です。

[Laravel]envファイルごとに処理を変更する

概要

ローカル環境と本番環境、テスト環境でDB処理を変えたかったのでその方法を起筆します。

実装

envファイルごとに分岐することで実装しました。

.envファイルと.env.productionファイルを用意してAPP_ENVを以下のようにする。

.env
APP_ENV=local
env.production
APP_ENV=production

controllerにAPP_ENVによって分岐させる処理を加える。

<?php

namespace App\Http\Controllers;

use App\Http\Requests\PostRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class PostController extends Controller
{
    public function store(PostRequest $request)
    {
        if(app()->environment('local')){
        //local環境で行いたい処理を記述
        }else if(app()->environment('production')){
        //production環境で行いたい処理を記述
        }
    }
}

まとめ

なんかもっとスマートな方法がありそうです。
知っていましたら教えてください。

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

【PHP8.1】プロパティ・引数デフォルト値でnewできるようになる

先日2021/03/02に、New in initializersというRFCが提出されました。

なにかというと、こんな構文が書けるようになります。

class Test {
    private $foo = new Foo();

    public function __construct( private Logger $logger = new NullLogger, ) {
    }
}

PHP RFC: New in initializers

Introduction

このRFCでは、プロパティやパラメータのデフォルト値など、イニシャライザの内部でnew式を使えるようにすることを提案します。

現在のPHPでは、以下のようなコードは許可されていません。

class Test {
    public function __construct(
        private Logger $logger = new NullLogger,
    ) {}
}

かわりに、このような書き方をしなければなりません。

class Test {
    private Logger $logger;

    public function __construct(
        ?Logger $logger = null,
    ) {
        $this->logger = $logger ?? new NullLogger;
    }
}

この文法では、実際のデフォルト値が何であるかがわかりづらく、引数にも本来不要なnull許容型を強制されてしまいます。

このRFCでは、この制限を緩和し、全てのイニシャライザ内でnewを使用できるようにします。

Proposal

イニシャライザ式の一部としてnew構文を許可します。
名前付き引数を含め、コンストラクタ引数として使用可能です。

// 全てOK
function test(
    $foo = new A,
    $bar = new B(1),
    $baz = new C(x: 2),
) {}

動的クラスや無名クラスは許可されません。
引数アンパックや定数式も許可されていません。

// 全てNG
function test(
    $a = new (CLASS_NAME_CONSTANT)(), // 動的クラス
    $b = new class {}, // 無名クラス
    $c = new A(...[]), // 引数アンパック
    $d = new B($abc), // 定数式?
) {}

影響を受けるのは、静的変数、定数およびクラス定数、プロパティ、引数のデフォルト値、そしてアトリビュートの引数です。

static $x = new Foo;

const C = new Foo;

#[AnAttribute(new Foo)]
class Test {
    const C = new Foo;
    public static $prop = new Foo;
    public $prop = new Foo;
}

function test($param = new Foo) {}

Order of evaluation

イニシャライザ式は、これまでは常にオートローダやエラーハンドラを通して副作用を制御することができました。
しかし、newをサポートすることで、副作用が第一級オブジェクトとなるため、どのような順番で評価されるかに注意を払う必要があります。
評価順は、イニシャライザの種類によって異なります。

  • 静的変数イニシャライザは、制御フローが宣言に到達した時点で評価されます。
  • グローバル定数のイニシャライザは、制御フローが宣言に到達した時点で評価されます。
  • アトリビュート引数は、ReflectionAttribute::getArguments()もしくはReflectionAttribute::newInstance()が呼ばれるたびに、左から順に評価されます。
  • 引数デフォルト値は、引数を渡さず関数を呼び出すたびに、左から順に評価されます。
  • プロパティデフォルト値は、オブジェクトをインスタンス化したときに、宣言した順に評価されます。これはコンストラクタが呼ばれる前に行われ、また評価中に例外がスローされた場合はデストラクタは呼ばれません。
  • 静的プロパティとクラス定数の評価順は不定です。現在は、該当のクラスが初めて使用されたときに評価されます。

Interaction with reflection

イニシャライザには、リフレクションを通してアクセスすることが可能です。

  • ReflectionFunctionAbstract::getStaticVariables() 静的変数の現在値を返します。まだ到達していない場合は即時評価します。
  • ReflectionParameter::getDefaultValue() 呼び出しごとにデフォルト値を評価します。
  • ReflectionParameter::isDefaultValueConstant()ReflectionParameter::getDefaultValueConstantName() 現在値を評価しません。
  • ReflectionClassConstant::getValue()ReflectionClass::getConstants()ReflectionClass::getConstant() クラス定数を返します。まだ到達していない場合は即時評価します。
  • ReflectionClass::getDefaultProperties()ReflectionProperty::getDefaultValue() 呼び出しごとに、静的プロパティと非静的プロパティを両方とも評価します。
  • ReflectionAttribute::getArguments()ReflectionAttribute::newInstance() 呼び出しごとにアトリビュート引数を評価します。
  • ReflectionObject::newInstanceWithoutConstructor() デフォルト値を評価して代入します。

Recursion protection

デフォルト値が再起になった場合は例外をスローします。

class Test {
    public $test = new Test;
}

new Test; // Error: Trying to recursively instantiate Test while evaluating default value for Test::$test

Trait property compatibility

同じプロパティの定義された複数のtraitを使用した場合、互換性チェックが行われ、両方に同じイニシャライザを要求されます。

trait T1 {
    public $prop = new A;
}
trait T2 {
    public $prop = new A;
}

class B {
    use T1, T2;
}

これは禁止されます。
なぜなら互換性の比較は===で行われるため、T1とT2のプロパティは異なるインスタンスと判断されるからです。
しかし、互換性チェックにおいて実際にnew構文を走らせるようなことはしたくないし、それによって副作用が発生する可能性もあります。

イニシャライザ式は大きくふたつ、静的と動的に分かれます。
静的は既存の全ての型で、動的はnewなどです。
traitのイニシャライザが動的である場合、常に互換性はないとみなされます。

Backward Incompatible Changes

後方互換性のない変更はありません。

Future Scope

このRFCは、newをサポートするだけという小さなものです。
しかし、同様の呼び出し式をサポートするための技術的基礎を築くものでもあります。

感想

いやあデフォルト値ありがたいですね。

引数デフォルト値が書けるようになることで、いちいちメソッド内で引数によって分岐する無駄が避けられるようになります。
またプロパティデフォルト値にはこれまでプリミティブ型しか設定できず、オブジェクトには設定できないという非常に半端な状態でしたが、この変更によって自然にデフォルト値が書けるようになります。

そういえば自身のネストは例外って書いてましたが、互いに持ち合わせた場合はどうなるのでしょうかね。

class A{
  private $b = new B();
}
class B{
  private $a = new A();
}

先日提出されたばかりのRFCなので、今後このような考慮漏れの調査および問題点の調整などを行い、その後投票で2/3以上の賛成という長いプロセスを経ないかぎり、PHP本体に取り込まれることはありません。
MLではさっそく様々な議論が交わされています。

しかし便利な機能であり、既存のPHP文法と矛盾せず自然に文法に組み込めること、さらに既にプルリクが存在すること、そして何よりAuthorがNikitaなので、そのうち受理されると思います。

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

php-fpmやモジュール版PHPについて調べてみた

はじめに

この記事はプログラミング初学者による備忘録用の記事であり、少しでも他の初学者のお役に立てればと思い書いています。

今回はphp-fpmについて基礎的な部分を調べてみました。
php-fpmについて調べると、モジュール版PHPとCGI版PHPという2つの存在がありましたので、そちらについても触れてみたいと思います。

間違いなどがございましたら、ご指摘のほどよろしくお願い致します。

php-fpm(FastCGI版PHP)とは

FPM ( FastCGI Process Manager ) は PHPのFastCGI実装のひとつで、 主に高負荷のサイトで有用な追加機能を用意しています。php-fpmはWebサーバーとは別のプロセス、PHP用の永続的なアプリケーションサーバーとして実行されます。

例: 複数のユーザーが1つのWebサーバを共有している時
CGI版PHPで動作しているとXさんとYさんのプログラムは、別ユーザーとして実行されているので、お互いの干渉を防ぐことができる。この観点からCGI版はセキュリティや安定性が優れていると言えます。

参考:PHP manual FastCGI Process Manager (FPM)

モジュール版PHPとは

WebサーバーのプロセスのなかでPHPを実行する方法、少し詳しくいうとApacheのrootユーザーでPHPを実行する方法です。
Webサーバー(Apache)のプロセスとして実行されるため、実行時に新しいプロセス(アプリケーションサーバー)が必要ありません。Apachaを使う場合、Apacheにモジュール(mod_php)を追加することで、アプリケーションサーバーの役割を持たせることができます。

例:複数のユーザーが1つのWebサーバを共有している時
モジュール版で実行される場合、XさんとYさんが作ったプログラムは、両方ともApacheのrootユーザーで実行されてしまいます。このような場合、XさんのプログラムがYさんのプログラムの実行を邪魔する可能性があると言えます。

参考:PHP manual Apache モジュールとしてインストール

CGIやFastCGIなど、よく分からない単語が出てきたので調べてみると、、、

CGIとは

Common Gateway Interface(コモン・ゲートウェイ・インタフェース、CGI)は、webサーバーがwebブラウザからの要求に応じてプログラムを動作させるための仕組みです。

ざっくりとした流れは

  1. webブラウザからの要求を受信したwebサーバーで、CGIの仕組みによってプログラムを起動(Webサーバー上で直接プログラム処理をしない)
  2. プログラムはwebブラウザから送られてきたデータやwebサーバー自身が持つデータなどからHTMLファイルなどを作成します。
  3. その後、プログラムで作成されたコンテンツは、webサーバーを通してwebブラウザに送信されます。

このような仕組みのため、webサーバーからは毎回異なったコンテンツを送信することができます。

ちなみに、CGIから呼び出されるプログラムはサーバーサイド・スクリプトと呼ばれ、PHPやRuby、Pythonなどが該当します。

参考:Web技術の基本

そして、このCGIを改良したのがFastCGIです。

FastCGIとは

役割自体はCGIと同じですが、CGIと比較すると処理速度の高速化と負荷の軽減が期待できる仕組みとなっています。

CGIでは、webブラウザからの要求に応じて起動したプログラムは、処理が終わり次第プログラム自体も終了します。
webブラウザからのリクエストが1回だけの場合、負荷などを考える必要はありませんが、大抵の場合はリクエストが何百回と繰り返されます。そのような場合、リクエスト毎にプログラムを起動、そして終了を繰り返していると非常に非効率でありwebサーバー側の負荷も増えます。

このような非効率かつ負荷が増える状態を改善したのが、FastCGIです。

FastCGIは、一度起動したプログラムは一定時間、メモリ上で起動したままの状態にしておくことができます。このような仕組みのおかげで、何百回とリクエストが来たとしても、プログラムを起動、終了する処理は1回で済むようになります。

参考:FastCGI「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

ざっくりとまとめると

php-fpm(FastCGI版PHP)
Webサーバー上で直接処理せずにCGIという仕組みを使うことで、プログラムを起動(アプリケーションサーバー)し処理を行い、webサーバーを通してwebブラウザの要求に応じる

モジュール版PHP
Apacheの拡張機能を使いwebサーバー上で直接処理をしてwebブラウザの要求に応じる

CGI版PHPとモジュール版PHPどちらを選択すべきか

CGI版PHPとモジュール版PHPのどちらを選択するべきかについてですが、私なりの判断基準が定まっていないので明確に述べることはできませんが、参考サイトのグラフを見る限り、

image.png

出典:boonex PHP https://www.boonex.com/trac/dolphin/raw-attachment/wiki/HostingServerSetupRecommendations/hssr-benchmark.jpg

php-fpm(FastCGI版PHP)とモジュール版PHPでは、処理速度にほどんど差がないことが分かりました。CGI版PHPはモジュール版PHPと比較すると遅いです。

従って単純に速度を重視するのであれば、FasCGI版PHPかモジュール版PHPを選択するべきなのかなと思いました。(実際にはセキュリティ面を考慮して適宜選択すべきだと思います)

最終的にはその他の特性を考慮したり使用条件を決めた上で選定すべきだと思います。

おわりに

個人的な開発ではセキュリティ面を重視したいので練習も兼ねてphp-fpmを使い、アプリケーションサーバとwebサーバーを作ることで、rootユーザーで管理されるのを避け、他のユーザーのプログラムに干渉しないようにしたいと思います。

php-fpmとはなんぞやと思い、色々と調べてみると、改めてプログラミングは奥が深いなと思わされました。

日々精進したいと思います。

参考文献

PHP manual FastCGI Process Manager (FPM)
PHP manual Apache モジュールとしてインストール
Web技術の基本
boonex PHP
FastCGI「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
nginx と PHP-FPM の仕組みをちゃんと理解しながら PHP の実行環境を構築する

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

Mockery\Exception\NoMatchingExpectationException を とりあえず解決する方法

Mockery をテストしているとやっているときに配列やオブジェクトを使っているとNoMatchingExpectationExceptionが出てきたときにどこが問題だかわからなくなる場合があります。

$this->guzzleClient = Mockery::mock(ClientInterface::class);
$requestResponse = $this->client->request('POST', $apiUrl, ['form_params' => $params]);
$this->guzzleClient
    ->shouldReceive('request')
    ->once()
    ->with('POST',
        'http://test.example.com/article/xxxx/comments',
        ['form_params' => $params])
    ->andReturn($response);
Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_0_GuzzleHttp_ClientInterface::request('POST', 'http://test.example.com/article/xxxx/comments', ['form_params' => [...]]). Either the method was unexpected or its arguments matched no expected argument list for this method

こういうとき、どの引数が一致せずにエラーが出るのかよくわからなくなります。
なので anyな引数を許可して実行してみることが大事です。

解決法: こんなときは Mockery::on を使ってすべての引数を許可する形にしてしまう。

どの引数が原因だかわからないときは、とりあえずエラーが出ずに動くようにすることを優先すると特定が早まった.

$this->guzzleClient = Mockery::mock(ClientInterface::class);
$requestResponse = $this->client->request(
    Mockery::on(function($actual) {
        return true;
    }),
    Mockery::on(function($actual) {
        return true;
    }), 
    Mockery::on(function($actual) {
        return true;
    })
);

例えば、弄っているうちに原因が第三引数にあることがわかったので下記のように修正すると↓

最終形はこんなの

$form_params = ['form_params' => $params];
$this->guzzleClient = Mockery::mock(ClientInterface::class);
$requestResponse = $this->client->request(
    'POST',
    $apiUrl,
    Mockery::on(function($actual) {
        return is_array($actual) && arrayHasKey($actual,'form_params') && is_array($actual['form_params']);
    })
);

参考文献:

その他

上記はチームメンバーの問題解決に口出ししただけなので、自分の手元で動かして動作確認してないことに注意

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

ci-phpunit-testの正式リリース(v1.0.0、v2.0.0、v3.0.0)

初の正式リリース

2021/03/03にCodeIgniter 3でPHPUnitでのテストを簡単に書くためのツール「ci-phpunit-test」のv1.0.0、v2.0.0、v3.0.0をリリースしました。

調べてみると、最初のリリースv0.1.0は2015/06/12でした。 ということで、「構想3か月、開発5年」みたいな長大なプロジェクトに結果的になりました。

ここまでで合計38回のリリース、18人のコントリビューター、Composerからの累計ダウンロード数37万回以上になりました。

ただし、GitHubスターがまだ536と少ないです。特に日本人は少ないので、ユーザーのかたは遠慮なくスターをガンガン押してください!

PHPUnitのバージョンとの対応

何故、3つもバージョンをリリースしたかというと、PHPUnit(とPHP)のバージョンアップに対応するためです。今のところそれ以外に機能的な差異はありません。

PHPUnitでテストを書いていますが、ブランチを分けないとテストが簡単に書けなくなりました。

ci-phpunit-test PHP PHPUnit
v3.0.0 PHP 7.3以降 PHPUnit 9.3以降
v2.0.0 PHP 7.2以降 PHPUnit 8.1から9.2
v1.0.0 PHP 5.4.0以降 (5.6以降を推奨) PHPUnit 4.3から7.5 (4.8以降を推奨)

今後

今後は、v3.xをメインのバージョンとして、必要があれば古いバージョンもメンテしていこうと思います。

CodeIgniter4への対応

なお、ci-phpunit-test for CodeIgniter4の作成予定はありません。これは、多くの機能がCodeIgniter4に含まれたため、必要性があまりないためです。

ただし、全ての機能がCodeIgniter4に含まれるわけではないので、以下のパッケージで不足する機能(モック作成のヘルパーメソッド、モンキーパッチ、$this->request)を提供していきます。

以下のテストコードのように既存のテストコードをあまり変更せずにCodeIgniter4でも使えるようになります。

モンキーパッチについて

最後に、今までモンキーパッチについて、きちんと説明を書いたことがないので、ここに記載しておきます。

モンキーパッチは自転車の補助輪のようなものです。プロダクトコードに問題がなければ、本来必要ありません。個人的にも私は自分が書いたコードでモンキーパッチを使ったことはありません。

モンキーパッチを使わないとテストが書けない場合は、設計に問題があると思ってほぼ間違いありません。設計を見直してください。

そうは言っても、サードパーティのツールがexit()していたり、過去のレガシーコードをまだ使わざるを得なくモックに差し替えられないというような場合のために、一応、機能を提供しています。

できるだけ早く、モンキーパッチを使わなくて済む環境を目指してください。

関連


クリエイティブ・コモンズ・ライセンス
この記事はCC BY-SA 4.0(クリエイティブ・コモンズ 表示 4.0 継承 国際 ライセンス)の元で公開します。

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

PHP5上級試験/準上級試験の上級合格に挑戦(10) 6章オブジェクト

オブジェクト

いわゆるクラスに関する章。
初級でもこの話は出てくるので、ここでは上級ならではのテーマで取り組んでいきたい。
ちなみにオブジェクトというのはデータ(プロパティ)と、データの操作(メソッド)を一体にしたもの。
これをnewすれば、クラスのコピー(インスタンス)ができて、自由に動かせる。
中身をいじられないようにカプセル化する。

おさらい

・アクセス権の設定(public,protected,private) functionの前におく。
 なければpublic。
・オーバーライド(上書き)→親クラスのプロパティやメソッドを子クラスで同じものを指定して上書きすること
・クラスのメソッドやプロパティの呼び出し方法(クラス名->メソッド名orプロパティ名)
・クラス名はシングルクォートで囲むことはできない
・new するクラスはかっこで囲んでも囲まなくてもOK $b = new Mail; or $b = new Mail(); どちらもOK
・クラス名は大文字・小文字が区別されず、大文字小文字があっても同様のクラス名となる
・クラス名にアンダースコア2つを頭につけることはできない
・クラス名やクラス内のメソッド・プロパティを呼び出すのに可変関数・可変変数の仕組みを使うことは可能
・デフォルト値には変数・変数展開を伴う文字列・ヒアドキュメント・式・関数・オブジェクトを作成するnewキーワードは使えない
・静的プロパティstatic
・静的プロパティやメソッドへのアクセスは クラス名::静的プロパティ名or静的メソッド名でできる
・サブ(子)クラスのメソッドから親クラスのメソッドを呼び出すにはparent::メソッド名()と記載
・同じクラスのメソッドはself::メソッド名()になる
・親クラス名・親クラスのメソッド名の頭にfinalキーワードを入れるとそれは子クラスで使えなくなる
・コンストラクタ・デストラクタはそれぞれ前もってプロパティの初期化をしたり、後始末で不要なリソースを解放する処理をしたりする仕様。デストラクタはパラメータは設定できない。そもそも後始末にパラメータは必要ないよね。

まちがいやすいところ

  • クラス名->メソッド名orプロパティ名とするところ、$をつけてしまうミスが多い($はつけない)
  • メソッドは引数がなくても()を付ける必要がある(呼び出すときもメソッド名()になる)

静的メソッド

・私も見るまで走らなかったが、静的プロパティはもともとあるが、静的メソッドもある。
静的メソッドのメリットについては下記を参照。@suinさんありがとうございます!
 参考:PHP: 静的メソッドは何のためにあるか?|@suinさん

$thisとselfの違い

  • $thisは自分のオブジェクトを指す。(インスタンス化された場合クラスではなくインスタンスを指すことになる)
  • selfは自分のクラスを指す。クラス定数、static変数については、インスタンス化せずに使用するのでこの場合は、selfを使ってアクセス
  • 最近では遅延静的束縛のstaticも使えるので現在はstaticを使うことが多い(ややこしいから歓迎)
  • $thisはメソッド内限定で記述できる特別な変数
  • 静的メソッド内部コードでは$thisは使えない
  • 参考:PHPの「self::」と「$this」の違いを現役エンジニアが解説【初心者向け】|TECH ACADEMYマガジン

上記の参考をもとに、問題を作成

index.php
class Father{
  function Test(){
    self::CallWho();
    $this->CallWho();
  }

  function CallWho(){
    echo '「息子よ」';
  }
}

class Son extends Father{
  function CallWho(){
    echo '「父よ」';
  }
}

$calling = new Son();
$calling -> test();

出力は
「息子よ」「父よ」

※selfはクラスのメソッドを実行、
$thisはそもそも、$calling自体が、Sonクラスのインスタンスのため、SonクラスのメソッドCallWhoを実行。
クラス名は大文字小文字を区別しない。

※よく出題されるので、selfと$thisの概念の理解をしておこう。

クラス内の未定義のプロパティへのアクセス

・そのクラスにsetメソッドやgetメソッドがあればクラス内で定義される
 以下例

index.php
class Test {
    function __set($name,$value){
      echo $name, 'に',$value, 'を書き込み';
      print '<br>';
    }
    function __get($name){
      echo $name, 'を読み取り';
    }
}

$TestA = new Test;
$TestA -> a = 1;  //aは上のクラス内で宣言していないけど設定できた
$TestB = $TestA -> a; //もちろんすぐ呼び出せます 

オブジェクトのコピー

・clone コピーしたいオブジェクト名でできる
・クラス内に__clone()メソッドがあるとコンストラクタのようにクローンされたときにすぐ呼び出される
・ただし、これは他のメソッドとはちがって外部から直接アクセスすることはできない(エラーになる)

では、下記の出力はどうなるか答えよ。
下記のコードの後に、「$z = $x -> __clone();」を入れるとどうなるか。

index.php
class Test {
  public $a;
  public $b;
  function __clone(){
    $this -> b =0;
  }

}

$x = new Test;
$x -> a = 2;
$x -> b = 2;

$y = clone $x;
$y-> a = 4;

echo 'x:', $x->a, $x->b;
echo 'y:', $y->a, $y->b;


正解は
x:22y:40
警告エラー(何も出力されない)

アクセス権について

  • public,protected,privateという3pだけ覚えても役に立たず、実際にコードで書くとどうなるのかをしっかり理解しよう
  • var は public の古い宣言方法
  • protectedは、そのクラス自身と継承した子クラスからアクセス可能可 ただし、そのクラスの親クラスは対象にならないので注意。 これを出題されたことがある
  • サブクラスから親クラスのprivateなプロパティにアクセスしようとすると、サブクラスに同名のプロパティができてしまう仕様がある。 以下実例
index.php
class X {
  private $a = 1;  
}
class Y extends X{
  function test(){
    $this->a=2;
  }
}
$y = new Y;
$y->test();
var_dump($y);


出力
object(Y)#1 (2) { ["a":"X":private]=> int(1) ["a"]=> int(2) }
つまり、スーパークラスのプロパティ\$aと、サブクラスで新たに作られたプロパティの\$aの2つが個別にあるってことになる。

抽象クラス・抽象メソッド・インタフェース

さて、上級ならではのテーマがこちら、抽象クラス・メソッド・インタフェース。
はっきり言って問題集の説明を読んでいてよくわからなかった。
いきなりぱっと出てきた感じ(笑)
書き方はまあ覚えたらいけると思うけど、そもそもどんなときに使うのだと言う話。

調べたらちょっとわかりやすい感じの説明をしてくれてるサイトを見つけました
ありがとうございます!!

参考:
抽象クラス(abstract)とインターフェース(interface)についての復習|いぬごやねっと
インターフェースと抽象クラスの使い分け、活用方法|@igayamaguchi
【詳解】抽象クラスとインタフェースを使いこなそう!!|@yoshinori_hisakawa

さてこれらをもとに、自分自身でも咀嚼して説明していく。
まず先に
抽象クラスは、一番上のサイトにも書かれていたけど、
たとえば犬・猫・豚とかだったら「動物」、えんぴつ、消しゴム、ものさしなら「文房具」と抽象化できるが、
それらは単純に上記に共通する項目があるから。
下にあるのは、一番上のサイトに書かれたことをまとめただけだが、
要するに共通する項目を全部まとめてクラスにして、ここから継承して動物としての共通データやメソッドをオーバーライドし、犬とか豚とかのクラスを作るということ

次に、抽象メソッドは、、、まあわかるね、上記の動物としてのメソッドが抽象メソッドにあたる。
インターフェースは、「窓口」ということで、これらの複雑なクラスの内部を見なくても、手続き(書き方)さえ知っていれば外部からそのインターフェースがあるクラスへのアクセスができるようになるというもの。
つまり、カプセル化は中身をいじられないようにするものだが、インターフェースはそのカプセル化のために用意されたもの。

さてなにはともあれ実践
まず何より抽象クラスは基本的に親クラスになりやすい、ということ。

  • 抽象メソッドを一つでももつ場合、そのクラスは抽象クラスとしなければならない
  • 抽象メソッド or 抽象クラスを作る場合、メソッド名orクラス名の頭に abstruct
  • 当たり前だが、抽象クラスのインスタンスは作れない。抽象クラスから継承したクラスのインスタンスを作ることになる なぜなら、たとえば、動物クラスだけでは実際の動物は作れず、それから先、うさぎクラスなど、うさぎだけしかない特徴を持つクラスを作らないといけないからで、そう考えると、サブの実体クラスからしかインスタンスが作れないということになる。
  • 抽象クラスを継承したクラスでは、抽象クラス内にあるメソッドを同じだけ作らねばならない。
  • そうでない場合は、その継承したクラス(サブクラス)も抽象クラスとしなければならない。そうして、実クラスを作るまで続くことになる
  • インタフェースはそのインタフェースが実装されているクラスにおいて持つべき機能を明示することができる。そして抽象クラスと同様、インタフェースを実装しているクラスでは、インタフェースにあるメソッドを同じだけ作らねばならない。そしてそうでない場合は、そのクラスは抽象クラスとしなければならない。
  • ただし、抽象クラスに抽象メソッドでない普通のメソッドが入っててもOKである
  • インタフェースを作る場合クラス名の頭に Interface、インタフェースを実装する場合 class クラス名 implements インタフェース名とする
  • インタフェースにはプロパティを設定することができない。プロパティ設定は実装したクラスで行おう。
  • ただし、定数設定はconstでできる。大文字で宣言するのが推奨されている
  • インタフェースでは他のインタフェースを継承する時、継承元と同名のメソッドは作れない
  • 複数のクラスの継承はできない。ただし、サブクラスのサブクラスは作れる
  • インタフェースの場合は複数実装することが可能。その場合はコンマで区切る。
  • インタフェースのインスタンスは作成できない。理由は抽象クラスと同じ。

あなたは誰のインスタンス?

結構出題される。
instanceof()という関数を使った問題で、例えば次のような問題が出る。

index.php
interface W{}
class X{}
class Y extends X implements W{}
class Z extends Y{}
class A extends B implements W{}
class B{} 
$y = new Y;
$z = new Z;
b = new B;
if ($y instanceof X){
 echo 'YはXのインスタンスである';
}

if ($y instanceof W){
 echo 'YはWのインスタンスである';
}
if ($b instanceof A){
 echo 'AはBのインスタンスである';
}

if ($b instanceof W){
 echo 'WはBのインスタンスである';
}


さて、インスタンスであると言える条件は
・オブジェクトがそのクラス、またはそのクラスのサブクラスのインスタンスである場合
・オブジェクトがそのインタフェースを実装するクラスおよび、そのクラスのサブクラスのインスタンスである場合
の2つ。
頭のは、クラスYはクラスXを継承したものであるため、クラスXが親、クラスYが子である。また、
親XはWをインタフェースにしている。
この場合、YはYのインスタンス、YはXのインスタンス、YはWのインスタンス、ということができる。

さらに、クラスZはクラスYを継承したものなので、クラスYが親、クラスZが子である。またクラスXは大親である。
この場合、ZはXのインスタンス、ZはYのインスタンス、ZはWのインスタンス、ということができる。

次にクラスBはクラスAを継承したものなので、クラスAが親、クラスBが子。
この場合、AはAのインスタンス、AはBのインスタンス、AはWのインスタンス、ということができる。
しかし、BはBのインスタンス、BはAのインスタンスではない、BはWのインスタンスではない。
なぜなら、Bは子であるため、親クラスのインスタンスは作れず、ましてやインタフェースを実装したのは親クラスで子クラスではないため。 

このように逆パターンやインタフェースをインスタンスにしたりするシーンでの出力を問われるややこしい問題が出題されるので注意。

子 extends 親 implements 子のインターフェース と考えること。
つまり最初が必ず子になり、extends の先が親。

タイプヒンティング(型宣言の強制)

関数及びメソッドが受け取る引数がどのオブジェクトor配列かをを指定することが出来る機能。
これによって誤った引数を引き渡すことを避けることができるというもの。
タイプヒンティングで指定されたものでないもの場合、「キャッチ可能な致命的エラー」が発生する
下の例では、キャッチ可能なエラーをキャッチしてタイプヒンティングエラーというのを出力している
ErrorExceptionではとらえられなかったのでTypeErrorでキャッチした

index.php
class X{}
class Y{}
class Z{
  function test(X $obj){
    echo 'OK';
   }
}

//エラーハンドラ
function exception_error_handler($error, $errstr, $errfile, $errline){
  throw new ErrorException($errstr,$errno, 0, $errfile, $errline);
}

//問題ない例
set_error_handler('exception_error_handler');
try{
$y = new Z();
$y -> test(new X);
} catch (TypeError $e){
  echo '例外発生';
}
//エラー例
try{
$z = new Z();
$z -> test(new Y);
echo 'OK';
} catch (TypeError $e){
  echo '例外発生';
}

クラスの調査

ここは関数を覚えたらOk
(1)クラスが定義済かどうかを調べる関数
(2)定義されたすべてのクラス名を配列で返す関数
(3)指定されたクラスのメソッド名(静的メソッド含む)の一覧を配列で返す関数
(4)指定されたクラスのプロパティとそれらのデフォルト値を配列で返す関数
(5)オブジェクトのスーパークラスを取得
(6)指定された値がオブジェクトかどうかを調べる関数
(7)指定されたオブジェクトのクラス名を取得する関数
(8)オブジェクト(またはクラス)が指定した名前のメソッドを持つかどうかを調べる関数
(9)指定されたオブジェクトについて、現在のスコープからアクセス可能なプロパティ(静的プロパティは含まない)とそれらの現在の値を取得する関数。つまり、設定したプロパティのみを返す。

正解
(1)class_exists()
(2)get_declared_classes()
(3)get_class_methods()
(4)get_class_vars()
(5)get_parent_class()
(6)is_object()
(7)get_class()
(8)method_exists()
(9)get_object_vars()

※(1)のclass_exists(class_name [,autoload = TRUE]);
デフォルトでオンになっているautoloadは、_autoload()をコールするかどうかを指定するもの。
※get_class_methodsはそのクラスだけでなく、親クラスのメソッドも合わせて返すが、同名のクラスは1つだけしか返さないので注意。つまり上書きしてるクラスは1つだけしか返さないってことになる。もちろん、外から呼び出した場合はpublicしか返さないので注意。→これを利用した問題が出題されている。
問題集の最後の仕上げ問題にもあるので解いておくと良い。

autoloadとはファイルを自動的に読み込む仕様。
クラスを作成する場合、再利用する性質上1クラスを1ファイルで管理することになるが、扱うファイル数が増えた時に、各スクリプトの先頭で一つ一つrequireしなければいけなくなる。そこでファイルを自動で読み込む仕組みであるautoloadを使用する。

シリアライズ

  • オブジェクトをバイトストリームにすること。これによってオブジェクトをファイルに保存可能になる
  • バイトストリームとは「byte型データの並び」のこと。下の例で出力すればわかる。ほかにも文字ストリームとかがある。
  • 要するに、配列とかをそのまま文字列とかにすることなく、配列のまま保存したり送受信できるようにすること
  • 下の例は配列だが、本来はデータファイルを引数にそのままシリアライズすることが多い
  • シリアライズ直前に呼び出されるのは__sleep(),直後に呼び出されるのは__wakeup()。インストラクタ・デストラクタみたいな働き方をする。シリアライズするクラスでこれらがあれば実行される
  • 参考:覚えておきたい「シリアライズ serialize」|hijiriworld Web ありがとうございます!
index.php
  $string = serialize(array(1,2,3));
  echo $string;
  print '<br>';
  var_dump(unserialize($string));

出力
a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}
array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) }

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

PHP 配列で複数の値をpostする方法

formでpost時配列で値を渡す方法

例)
index.php


<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <form action="eat.php" method="post" >
  <select name="lunch[]" id="" multiple>
  <option value="pork">buta</option>
  <option value="befe">ushi</option>
  <option value="dog">inu</option>
  <option value="chcken">tori</option>
  <option value="snake">hebi</option>
  </select>
  <input type="submit" name='subnit'>
  </form>
</body>
</html>

eat.php

<?php 
$a = $_POST['lunch'];
if(isset($_POST['lunch'])){
    foreach($_POST['lunch'] as $choice){
      print "you want $choice .<br/>";
    }
  }

結果
スクリーンショット 2021-03-08 10.52.06.png
3つの値をshiftで選択すると、、

スクリーンショット 2021-03-08 10.52.10.png

上記の表示がされる。

ポイント

selectタグのname属性をname='lunch[]'[]にしている。
これが無くてname='lunch'では複数をshiftで選択してpostしても複数の値を$_POSTで取得できない。

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

laravelアプリ作成時のコマンド実行で非常に時間がかかるのでダウンロードの詳細を出力してみる

目的

  • laravelアプリ作成時のコマンドが全然終わらないので詳細を出力するオプションをつけて実行してみた

環境

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

情報

  • 筆者はMacに直接laravelアプリ動作環境を構築してlaravelアプリの作成を行った。

困りごと

  1. 下記コマンドを実行してlaravel8のアプリを作成しようとしたが全然処理が完了しない。

    $ composer create-project "laravel/laravel=8.*" todos
    

ダウンロード進捗状況を逐一出力するようにしよう

  1. -vvvというオプションを付けることによりコマンドの作業進捗が出力される。

    $ composer create-project "laravel/laravel=8.*" todos -vvv
    
  2. フリーズすることもなく着々と依存性の解決とパッケージのダウンロードが行われていた。これなら時間が解決してくれそうなので気長に待つこととする。

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

PHP printfとsprintfの使い方

printf と sprintfの違い

・printfは第一引数に指定した数値を第二引数以降で指定した値でフォーマットし出力)。
出力する為echoは不要。

・sprintfはprintfと考え方は同じで出力ではなく値を返すので出力にはechoなどが必要

どちらも変数に格納することも可能。

例1)

$price =5;
$tax = 0.075;

printf('このお皿は %d円で%sさんが買いました',700,'田中');//このお皿は 700円で田中さんが買いました
echo sprintf("このお皿は %d円で%sさんが買いました",700,'田中');//このお皿は 700円で田中さんが買いました

例2)

printf('このお皿の値段は $%.3fです',$price*(1+$tax));//のお皿の値段は $5.375です
 printf('
%06d左の数字では6桁の数字の1桁目に0が、/ %02d次の数字には2桁のうち1桁目に0が 
/ %01dさらに次は0はつかず1桁目には引数の1が表示されます。/ %03d最後も0はつかず引数の321が表示される。
つまり0は引数の桁数+1桁の数値で指定する必要があります'
,12345,3,1,321);

//出力
//012345左の数字では6桁の数字の1桁目に0が、/ 03次の数字には2桁のうち1桁目に0が 
// / 1さらに次は0はつかず1桁目には引数の1が表示されます。/ 321最後も0はつかず引数の321が表示される。つまり0は引数の桁数+1桁の数値で指定する必要があります

上記第二引数以降では%d、%sなど第一引数で指定した値に対し、左から順番にフォーマットする値を設定している。
%○で設定している値には以下の通りの意味がある

PHP指定子

スクリーンショット 2021-03-08 9.28.18.png

今日は以上。

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

Trying to get property of non-objectエラー

laravelでビューを作成している際に、Trying to get property of non-objectというエラーが出ました。
これは、アロー演算子の先に値がないというエラーです。

これを解決するには、optional()関数を使います。

{{ optional($post->category)->name }}

ヘルパー関数optional()は第二引数と共に利用し、第一引数(optionalの中)がnullでない時、第二引数を実行します。

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

Trying to get property of non-objecエラー

laravelでビューを作成している際に、Trying to get property of non-objecというエラーが出ました。
これは、アロー演算子の先に値がないというエラーです。

これを解決するには、optional()関数を使います。

{{ optional($post->category)->name }}

ヘルパー関数optional()は第二因数と共に利用し、第一因数(optionalの中)がnullでない時、第二因数を実行します。

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

【オススメ書籍・教材あり】共同開発前に知っておかないと損すること5選

今回は、私が共同開発を経験し、開発前に知っていると開発がもっとスムーズだったなと思うことを5つにし記述していきたいと思います!

読むのをオススメする人

・共同開発をこれから始める人
・実務に初めて入る人

共同開発前に知っておかないと損すること5選

考え方
1. 抽象と具体
コミュニケーション
2. 進捗確認
3. メンバーとのコミュニケーション
技術面
4. 検索能力
5. Git・GitHubの使用方法

これらの5つです。

では一つ一つ説明をしていきます!

考え方:抽象と具体

これすごく大事です!!

抽象とは、解釈の自由度が高く・応用が効く
具体とは、解釈の自由度が低い・応用が効かない
ものです。

これを聞くと、どういう意味???

となると思いますので、今から抽象と具体について分けて説明していきます!

抽象とは

抽象とは、「いくつかの共通の事柄を一括りにしたもの」と考えてください

例:掃除
あるパーティの後、片付けをしないといけない時に支配人に
「テーブルにあるコップをゴミ箱に床に、落ちているゴミをゴミ箱に入れて、床をホウキではいてください」と言われると聞いていてとても煩わしくありませんか?

普通に掃除してというとすむ話なのにと

これこそが抽象です!

抽象を使うことによりお互いがある状況・知識を知っているととてもコミュニケーションが円滑に進める事ができます!

それに加えて思考がとてもシンプルに整理する事ができるというメリットつきです!

開発でわからないものがある時に要はこれは何と何を使って機能を実装するのかがわかると後で出てくる検索力が格段に上がります!

しかし、抽象概念だけを極めたとしてもそれだけでは、相手に何か質問をする時などに解釈の相違が出てきてしまいます!
その時に大事になってくるのが具体的な考え方です!

具体とは

具体とは、先ほどの抽象と逆の考え方で細かく細分化していく考え方になります!

私を例に出してみると
人間→男→Daichi
このDaichiというのが具体的な考え方ということになります!

具体的な考え方がなぜ必要なのか??
それは質問をする時・何か機能を実装する時に必要になるからです!

プログラミング初学者は、必ずエラーに悩まされます←これは絶対です!

しかし、最初の方は何でエラーが起きているか全くわかりませんその時に誰かに相談する時大体Slackなどのアプリで文字ベースで質問になると思います!

質問内容
「エラーが出てきてわかりません」
では質問された方も何の原因でエラーが起きているかがわかりません

その時に具体的に説明ができると回答者も回答がしやすくなります!

    オススメの質問方法
  1. エラーが起きた時点の状況←できるだけ詳細に書く
  2. エラーのメッセージの画像とエラーメッセージのコードを貼り付ける←コピーができる様に
  3. 何を試したか
  4. そこまでに至ったかを書く←自分の理解度を深めるためにも良い

具体の考え方ができるととても質問がやりやすくなります!

これはどんな時にも言えます!
状況:算数の5+5がわからない時の質問
A「算数がわからないです!」
B「算数の何がわからないの??」
A「足算!!」
B「足算のどの問題がわからないの??」
A「5+5がわからない!!(何でわかってくれないの)」
B(初めからそう言ってくれれば良いのに)

この様な状態が起きるのを避けるためにも具体的に状況を説明できる様にすると良いです!

なぜ抽象と具体を分ける必要があるのか

まとめると、
何か機能を作る時に
検索なら検索・いいね機能ならいいね機能とその機能を作るためのおおよそ作り方は検索するとわかりますが、検索機能であればそのまま自分の開発に貼り付けてうまく動きません個別により使うカラムとかが違うからです!

なので、何か機能を実装したいときは、どの様に作られているかの大枠を検索で学び、自分の機能には、それをどの様に使っていくかを考えていくのが大事になります!

なので、抽象的な考えだけでもダメですし、具体的な考えができるだけでもダメと言うことになります。

オススメ書籍:具体抽象トレーニング

コミュニケーション:進捗確認

ここからはコミュニケーションについて書いていきます!

プログラム書いていくだけなのにコミュニケーションと思った方もいるかもしれませんが、これはとても大事なことです!!

なぜなら、
プログラミング書いているのは、人だからです!

結局、
個人で作れるアプリには限界があります。
会社単位になるとみんなで一つのもの作るのにしっかりコミュニケーションを取らないと次の機能実装がやりにくいなどがあるからです。

進捗確認することは相手を安心させるのにもつながります!

そのためにオススメのアプリは、Trelloです!!

これは、ほとんどの方が使っていると思うのですが、軽く説明をすると
タスクごとにカードを作り
完成までの日にち・誰が作成中か・進捗報告もできる
優れものです。

僕も共同開発中は、とても使わせていただきました!!

メンバーとのコミュニケーション

これは、今どんなエラーで悩んでいるか・どこまで進んだかの共有するということです!

なぜ必要か??
共同開発で大切なことは悩みの共有です!

正直、共同開発をしていると自分の知らないエラーが必ず出てきます。
その時に一緒に考える事ができるメンバーがいるのは、とても助けになります!

僕自身それで何度も救われてきました!

自分が知らなくても相手ならその問題を知っている事が多々ありますその逆に相手が知らないことを自分が知っている場合があります!

この様に悩みを共有することによりより効率的に作業を進める事ができます!

自分がわからないエラーが出た際には
相手は、アウトプットになるために勉強になるし、自分は相手になるべくわかりやすく質問をしないといけません。
お互いにメリットがあります!!

さらにプログラミングを書いていると1人の時間が多く投げ出してしまいたくなる時もメンバーとコミュニケーションをとっているとメンバーも頑張ってるし自分も頑張らなければという感情になります!

やる気も大事ですが、そのやる気を維持させるための環境づくりも同じくらい大事になってきます!

技術:検索能力

これは1番大事と言っても過言ではないと思います!

プログラミングで全ての機能を覚える事は不可能です!!

なので、基礎概念などをしっかりと覚えると後は、いろんな機能の実装の度に自分でわからないことを調べそれを自分の実装したい機能の時にはどの様に応用するかを考えないといけません!

その際にも必要になってくるのが、抽象と具体です!

調べる際に自分の機能の具体的な内容を書き綴ってもあなたの機能にそのまま使える物はありません!

なので、まず何がわからないか細分化する必要があります!

カレーを作りたい時は、カレーの作り方を調べてその後に必要な食材を一つずつ列挙していく様に

プログラミングの機能実装も実装したい機能を検索しそれに必要なことを一つづつ調べていく

必要があります!

疑問に思った時に使える検索エンジン・サイト:


  • Google
    検索をする際は、Googleでほとんど全てを網羅できます!

  • Udemy(有料)
    動画で学ぶ事ができとても内容が濃いいです!
    教材購入時期は、月に何度かくるセールの時がお得!!

  • GitHub
    わからない事があれば他の人がすでに作った機能のコードを読み参考にする事ができます!

  • Qiita(無料)
    まさに今僕が書いているサイトです!
    いろんな人が自分の経験・知見などを書いてくれています!

上記のものをしっかりと使いこなす事ができかつそれを自身の問題に応用ができる様になるとどんなことでも解決できるはずです!

Git・GitHubの使用方法

これまでは、個人でやってたからGitHubなんて使ったことないという人もいると思いますが、共同開発では必ず必要になってきます!

実際にGit・GitHubの使ったことありますかという質問は就職の面接でも聞かれるくらい大事です!

Git・GitHubとは何なのか??

バージョン管理システムです!

どう言うこととなると思いますが、セーブ機能と考えてくれていると良いです!

これらを使うことによりいつでも戻りたい地点に戻ったり・エラーを確認したのちに新しく機能を実装できる様になります!

例えば、
アプリをリリースした際に他に追加機能を実装したい場合アプリをリリースしたファイルをそのまま編集をしていくと実装しきるまでそのアプリは、使えなくなる可能性があります!
さらに、追加機能をしている途中でファイルがぐちゃぐちゃになった際に前の状態に戻りたい時に一つ一つ戻すのは面倒ですし、大規模になるとどこを変更したかわからなくなり戻せなくなる可能性があります!

それを回避できるのがGit・GitHubです!
一つのタスクができるととりあえずセーブをしておき他のタスクに取り掛かる事ができます!
これをコミット・ブランチをきると言います

さらに、今やっているタスクがうまくいかなく前の状態に戻り作りたい際に前のセーブポイントに戻り再度作り直す事ができます!
これをチェックアウトと言います!

今回は、詳しくは書きませんがこれらはとても共同開発をする際には必須になりますので、共同開発をする前は必ず触りだけでもやっておくことをオススメします!


オススメの教材:
Udemy無料(Git:初めてのGitとGitHub)
Udemy有料(Git: もう怖くないGit!チーム開発で必要なGitを完全マスター)



こちらの2つの講座は、僕も受けましたが本当にわかりやすくかつ本質的な内容でしたのですごくオススメです!!

終わりに

今回は、共同開発前に僕がしておけばよかったなと思ったこと5選について書かせていただきました!

言語の基礎能力をつけるなどは、最低限ある状態を想定して書いたので今回の記事には書いておりません!
これが少しでも誰かに役に立つ幸いです!

最後までお読みいただきありがとうございました!!

*この記事は、アフィリエイトリンクは使っておりません

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

configファイルに好きなファイルを作成したら...

基本的なことになりますが、laravelでconfig配下に好きなファイルを作成したら、下記コマンドを実行しなければ反映されません。

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

laravel8マルチログイン

はじめに

フロント画面と管理画面でログイン認証を分けたい場合の
実装方法を記載する。
業務ではオリジナルテンプレートを使用するパターンが多いので、
『laravel/ui』なし、独自コントローラーでの実装を目指します。
ログイン機能を作成します。
※ログアウトやリマインダーは今回はやりません。

やりたいこと

users:顧客
admins:管理者
別々のログイン画面でログイン認証可能にする。

環境

PHP 7.3.2
Laravel Framework 8.5.12
MariaDB 10.1.38

事前準備

Composerをインストール
https://weblabo.oscasierra.net/php-composer-windows-install/

laravel8環境構築

LaravelをComposerでインストール
composer create-project --prefer-dist laravel/laravel LaravelMultiLogin "8.*"
※しばらく時間がかかります

ディレクトリ移動
cd LaravelMultiLogin
サーバ起動
php artisan serve

ブラウザでhttp://localhost:8000/
にアクセスしてLaravelって表示されれば成功

ライブラリインストール

・fortify
composer require laravel/fortify
・laravelcollective
composer require laravelcollective/html

各種設定

①config/app.php 言語設定変更

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

キャッシュクリア
php artisan config:cache

②「.env」設定変更(DB接続先の環境に合わせて変更)

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db_name
DB_USERNAME=root
DB_PASSWORD=

③エラーメッセージ日本語化

下記、からダウンロード
https://github.com/Laravel-Lang/lang/tree/master/src/ja

メッセージファイルを配置
C:\XXX\LaravelMultiLogin\resources\lang\ja\auth.php
C:\XXX\LaravelMultiLogin\resources\lang\ja\pagination.php
C:\XXX\LaravelMultiLogin\resources\lang\ja\passwords.php
C:\XXX\LaravelMultiLogin\resources\lang\ja\validation-inline.php
C:\XXX\LaravelMultiLogin\resources\lang\ja\validation.php

validation.php
'attributes' => [
    'email' => 'メールアドレス',
    'password' => 'パスワード'
],

テーブル作成

マイグレーション作成

database\migrations\XXXX_XX_XX_XXXXXX_create_admins_table.php
    public function up()
    {
        Schema::create('admins', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

マイグレーション実行
php artisan migrate

SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQL: alter tableusersadd uniqueusers_email_unique(email))

上記エラーが発生した場合は、下記設定をし再度実行
※一度DB内で作成されたテーブルを全て削除

app\Providers\AppServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema; // 追加

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
        Schema::defaultStringLength(191); // 追加
    }
}

ログインデータ作成

seedファイル作成
php artisan make:seed MultiAuthTableSeeder\MultiAuthTableSeeder.php

        \DB::table('users')->insert([
            [
                'name' => 'user',
                'email' => 'user@example.com',
                'email_verified_at' => now(),
                'password' => \Hash::make('password'),
                'created_at' => now(),
                'updated_at' => now()
            ],[
                'name' => 'user2',
                'email' => 'user2@example.com',
                'email_verified_at' => now(),
                'password' => \Hash::make('password'),
                'created_at' => now(),
                'updated_at' => now()
            ]
        ]);
        \DB::table('admins')->insert([
            [
                'name' => 'admin',
                'email' => 'admin@example.com',
                'email_verified_at' => now(),
                'password' => \Hash::make('123456789'),
                'created_at' => now(),
                'updated_at' => now()
            ],[
                'name' => 'admin2',
                'email' => 'admin2@example.com',
                'email_verified_at' => now(),
                'password' => \Hash::make('123456789'),
                'created_at' => now(),
                'updated_at' => now()
            ]
        ]);
database\seeders\DatabaseSeeder.php
    public function run()
    {
        // \App\Models\User::factory(10)->create();
        $this->call(MultiAuthTableSeeder::class); // 追加
    }

seed実行
php artisan migrate:fresh --seed

コンフィグファイルを変更

config/auth.php
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'admins' => [               // 追加
            'driver' => 'session',  // 追加
            'provider' => 'admins', // 追加
        ],                          // 追加

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        'admins' => [                               // 追加
            'driver' => 'eloquent',                 // 追加
            'model' => App\Models\Admin::class,    // 追加
        ],                                        // 追加
    ],

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
        'admins' => [                       // 追加
            'provider' => 'admins',         // 追加
            'table' => 'password_resets',   // 追加
            'expire' => 60,                 // 追加
            'throttle' => 60,               // 追加
        ],
    ],

キャッシュクリア
php artisan config:cache

モデル作成

php artisan make:model Admin -m
※Userモデルはあるため不要

app/Models/Admin.php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable; // 追加

class Admin extends Authenticatable // 変更
{
    use HasFactory;
}
routes/web.php
// ユーザーログイン
Route::get('user_login', [\App\Http\Controllers\UserLoginController::class, 'showLoginForm']);
Route::post('user_login', [\App\Http\Controllers\UserLoginController::class, 'login']);

Route::prefix('user')->group(function () {

    Route::middleware('auth:web')->group(function () {
        Route::get('dashboard', function () {
            Auth::guard('web')->logout();
            return 'ユーザーでログイン';
        });
    });
});

Route::prefix('admin')->group(function () {
    // 管理者ログイン
    Route::get('admin_login', [\App\Http\Controllers\AdminLoginController::class, 'showLoginForm']);
    Route::post('admin_login', [\App\Http\Controllers\AdminLoginController::class, 'login']);


    Route::middleware('auth:admins')->group(function () {
        Route::get('dashboard', function () {
            Auth::guard('admins')->logout();
            return '管理者でログイン';
        });
    });
});

コントローラー作成

php artisan make:controller UserLoginController

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

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserLoginController extends Controller
{
    //
    public function showLoginForm()
    {
        return view('user.login');
    }

    public function login(Request $request)
    {
        $this->validate($request, [
            'email' => 'required|email', 'password' => 'required',
        ]);

        $credentials = $request->only(['email', 'password']);

        if (\Auth::guard('web')->attempt($credentials)) {

            return redirect('user/dashboard'); // ログインしたらリダイレクト

        }

        return back()->withInput($request->only('email'))
            ->withErrors([
                'email' => ['認証情報が記録と一致しません。']
            ]);
    }
}

view作成

未ログイン時のリダイレクト先変更

/app/Exceptions/Handler.php
use Illuminate\Auth\AuthenticationException; // 追加

    protected function unauthenticated($request, AuthenticationException $exception)
    {
        if ($request->expectsJson()) {
            return response()->json(['message' => $exception->getMessage()], 401);
        }
        if ($request->is('admin') || $request->is('admin/*')) {
            return redirect('admin/admin_login');
        }
        return redirect('user_login');
    }

unauthenticated メソッド追加

resources/views/user/login.blade.php
<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ログイン</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" crossorigin="anonymous">
</head>
<body>
<div id="app">
    <div class="container">
        <div class="row justify-content-center mt-5">
            <div class="col-md-6">
                <div class="card">
                    <div class="card-header">ユーザーログイン</div>
                    <div class="card-body">
                        {{ Form::open(['method' => 'post']) }}
                            <div class="form-group">
                                {{ Form::label('email', 'メールアドレス') }}
                                {{ Form::text('email', null, [
                                    'class' => 'form-control' . ($errors->has('email') ? ' is-invalid' : '')
                                ]) }}
                                @error('email')
                                    <div class="invalid-feedback">
                                        {{ $message }}
                                    </div>
                                @enderror
                            </div>
                            <div class="form-group">
                                {{ Form::label('password', 'パスワード') }}
                                {{ Form::password('password', [
                                    'class' => 'form-control' . ($errors->has('password') ? ' is-invalid' : '')
                                ]) }}
                                @error('password')
                                    <div class="invalid-feedback">
                                        {{ $message }}
                                    </div>
                                @enderror
                            </div>
                            <div>
                                <button type="submit" class="btn btn-primary">ログイン</button>
                            </div>
                        {{ Form::close() }}
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

php artisan make:controller AdminLoginController

app/Http/Controllers/AdminLoginController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AdminLoginController extends Controller
{
    //
    public function showLoginForm()
    {
        return view('admin.login');
    }

    public function login(Request $request)
    {
        $this->validate($request, [
            'email' => 'required|email', 'password' => 'required',
        ]);

        $credentials = $request->only(['email', 'password']);

        if (\Auth::guard('admins')->attempt($credentials)) {

            return redirect('admin/dashboard'); // ログインしたらリダイレクト

        }

        return back()->withInput($request->only('email'))
            ->withErrors([
                'email' => ['認証情報が記録と一致しません。']
            ]);
    }
}
resources/views/admin/login.blade.php
<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ログイン</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" crossorigin="anonymous">
</head>
<body>
<div id="app">
    <div class="container">
        <div class="row justify-content-center mt-5">
            <div class="col-md-6">
                <div class="card">
                    <div class="card-header">管理者ログイン</div>
                    <div class="card-body">
                        {{ Form::open(['method' => 'post']) }}
                            <div class="form-group">
                                {{ Form::label('email', 'メールアドレス') }}
                                {{ Form::text('email', null, [
                                    'class' => 'form-control' . ($errors->has('email') ? ' is-invalid' : '')
                                ]) }}
                                @error('email')
                                    <div class="invalid-feedback">
                                        {{ $message }}
                                    </div>
                                @enderror
                            </div>
                            <div class="form-group">
                                {{ Form::label('password', 'パスワード') }}
                                {{ Form::password('password', [
                                    'class' => 'form-control' . ($errors->has('password') ? ' is-invalid' : '')
                                ]) }}
                                @error('password')
                                    <div class="invalid-feedback">
                                        {{ $message }}
                                    </div>
                                @enderror
                            </div>
                            <div>
                                <button type="submit" class="btn btn-primary">ログイン</button>
                            </div>
                        {{ Form::close() }}
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

参考記事

https://blog.capilano-fw.com/?p=8159
https://www.webopixel.net/php/1665.html

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