Skip to content

Instantly share code, notes, and snippets.

@Ryomasao
Last active August 6, 2020 07:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ryomasao/80d42e157d9282a8074838d7c40556d4 to your computer and use it in GitHub Desktop.
Save Ryomasao/80d42e157d9282a8074838d7c40556d4 to your computer and use it in GitHub Desktop.
PHP環境設定メモ

PHPの環境周りメモ

https://qiita.com/castaneai/items/d5fdf577a348012ed8af

とりあえず動かしたいんじゃってときは、以下が便利 https://gist.github.com/Ryomasao/d0eecd7c5311b9c8984d6d8fba098ac9

$ docker run -d -p 8000:80 --name my-apache-php-app -v "$PWD":/var/www/html php:7.2-apache
$ docker exec -it コンテナ名 bash

Xdebug

リモートデバックなので、ローカル環境にPHPはいらない。 リモート環境とPHPをあわせようとしてanyenv→phpenv導入しようとしたけど、Mojaveからめちゃくちゃめんどくさくなってて萎えた。

リモートデバックそのものは、この記事がとてもわかりやすい https://qiita.com/castaneai/items/d5fdf577a348012ed8af

.vscode/launch.json

{
    "version": "0.2.0",
    "configurations": [
      {
        "name": "XDebug on vagrant",
        "type": "php",
        "request": "launch",
        "port": 9000,
        "pathMappings": {
          // {vagrant上のdocument root}:{ローカルのdocument root}
          "/mnt/fuelphp": "${workspaceFolder}/fuelphp"
        },
        // BreakPointとまんねえって時はとりあえず、entry時にとまるか確かめよう
        "stopOnEntry": false
      }
    ]
}
xdebug.remote_enable = 1
; このホストのIPはvagrant内から見た、ホストOSのIP
; なんでこの値になるんだっけ。
xdebug.remote_host = "10.0.2.2"

autostartの設定をしてもいいけど、クエリパラメータ指定すると起動してくれる。いいね! http://localhost:8000/?XDEBUG_SESSION_START=test

dockerで使う場合は以下の設定にした。 この辺も参考になりそう。 https://stackoverflow.com/questions/52579102/debug-php-with-vscode-and-docker

普通にコンテナ起動して、VScodeでデバッカ起動して、phpunit実行したら止まった。すごい。 "stopOnEntry": falseをtrueにしてもエラーも吐かずに、デバックできないときがあって、コンテナ落としあげしたら解消した。

; 参考にさせていただいた記事
; https://qiita.com/gigosa/items/90431be7a6a79db78480
[xdebug]
; この設定は、docker-php-ext-enable xdebugを行うことで、以下に書かれる
; /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
#zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20190902/xdebug.so
xdebug.remote_enable=1
xdebug.remote_autostart=1
xdebug.remote_connect_back=0
; ホスト側のIP
; host.docker.internalはdockerのhostマシンのIPを解決する
; LAMP環境のDockerを提供するDevitypeだと、docker for Macでhost.docker.internalが使えねえ的は話があった
; 自分の環境だととりあえず大丈夫だった
; https://devilbox.readthedocs.io/en/latest/intermediate/configure-php-xdebug/macos/vscode.html#configure-php-xdebug-mac-vscode
xdebug.remote_host=host.docker.internal
; 空いているport番号(xdebugのデフォルトは9000)
xdebug.remote_port=9001
;xdebugの出力するログの場所
xdebug.remote_log=/tmp/xdebug.log

VSCode + Dockerでxdebug

リポジトリ https://github.com/Ryomasao/hello-graphql/tree/master/server

1.設定済みのプロジェクトルートに移動

$ cd hello-graphql/server
$ code .

2.docker拡張から、graphqlコンテナを起動して、Remote拡張からアタッチしてコンテナの中に入る。

3.VSCode側で、デバックを開始する 4.コンテナの中でphpを実行するとステップ実行できる。最高だ。

PHPのメモ

こっちにも書いてる。 https://github.com/Ryomasao/hello-graphql/tree/master/server

PHPに関することはこっちにも転記する。

モジュールの考え方について

php では、別ファイルは読み込むにはrequireとincludeがある。 requireは指定されたパスのファイルがなかったり、ファイル内で構文エラーがあったりすると、FatalError になるのに対し、includeは waning でになるとのこと。

基本的にrequireを使えばいい気がする。

index.php

require('./moduleA.php");

Composer の仕組み

まずはcomposerは置いておいて基本の話

https://github.com/Ryomasao/hello-graphql/blob/master/server/app/www/autoloader/index.php

php の組み込み関数であるspl_autoload_registerで実装されてる。 これは、クラスを new する際に呼び出す関数を設定することができる。

これを使って、ある程度自動でrequireする仕組みを構築することができる。

class ClassLoader
{
    private $dirs;

    public function registerDir($dir)
    {
        $this->dirs[] = $dir;
    }

    public function register()
    {
        // spl_autoload_registerは、定義されてないクラスがインスタンス化されたときに呼び出される
        // ここでは、loadClassメソッドが呼び出される

        // sql_autoload_registerには、callableな値を渡す
        // https://www.php.net/manual/ja/language.types.callable.php
        spl_autoload_register(array($this, 'loadClass'));
    }

    public function loadClass($class)
    {
        // loadClassメソッドは、new Class名のクラス名をもとに、requireを実行する。
        // requireする際のディレクトリはregitserDirで事前に設定しておく必要あがる。
        print("load class:$class\n");
        foreach ($this->dirs as $dir) {
            $file = "$dir/$class.php";
            if (is_readable($file)) {
                require($file);
                return;
            }
        }
    }
}

$loader = new ClassLoader();

$loader->register();
$loader->registerDir('./otherDir');

$sample = new Sample();

composer

composerも同じようなことをしてる。

https://github.com/Ryomasao/hello-graphql/tree/master/server/app/www/hellophp

composerを使うときは、index.phpとかに require('vendor/autoload.php');を書くよね。 これを見てみると、とあるファイルをrequireして、クラスメソッドを実行してる。

<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitf3a755c714b88f92b071e828a83148e4::getLoader();

autoloader_real.phpをみると、spl_autoload_registerがいる。 直後にunregisterしてるのであれ、っておもったら、\Composer\Autoload\ClassLoaderを読み込むためだけに設定してるっぽい。

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInitf3a755c714b88f92b071e828a83148e4', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInitf3a755c714b88f92b071e828a83148e4', 'loadClassLoader'));

classLoaderをみると、こっちで再度registerしてた。

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

composerはnew App/Hogeってすると、spl_autoload_registercomposer.jsonでマップした情報をもとにクラスを探してくれる。 マップ情報は実行時に json をみるとかじゃなくってcomposer dump-autoloadを実行することで、マップ情報が埋め込まれた php を生成してるんだ。

{
  "autoload": {
    "psr-4": {
      // Appってつくものは、services配下にあるっていうマップ
      "App\\": "services/"
    }
  }
}

クラスだけじゃなくって、以下のように helper 関数なんかも登録することができる。

{
  "autoload": {
    "psr-4": {
      "App\\": "services/"
    },
    "files": ["helper/functions.php"]
  }
}

namespaceとcomposerについて

以下のように、Appのnamespace配下にない、Carクラスを参照しようとしても、そんなクラスはねえよでこける。

composer.json

{
  "autoload": {
    "psr-4": {
      "App\\": "services/"
    },
  }
}

index.php

require('vendor/autoload.php');
$car = new App\Car('car');

services/Car.php

<?
  class Car {}

composerは、composer.jsonにかかれているマップ情報をもとにクラスを探しにいくから、App/CarでCar.phpのinlcude自体は実はできている。 ただ、Appのnamsespace配下にないから、クラスがみつかんないよってなる。 namespaceとマップ情報の構造は合わせないとだめなんだね。

FuelPHP

Fuelphpの流れを見てみる

index.php

// defineでAPPPATH、COREPATHとかのパスを定数として定義


// Fuelのcoreとして、autloaderをもってるっぽい。
// 中を見るとspl_autoload_registerしてた。
require COREPATH.'classes'.DIRECTORY_SEPARATOR.'autoloader.php'

autoloader.php

	protected static function init_class($class, $file = null)
	{
    // requireじゃなくって、includeしてるっぽ?
		// include the file if needed
		if ($file)
		{
			include $file;
		}

    // おもしろいのが、constructorとは別に、init関数を実行してる
    // インスタンス化のタイミングってより、初回autoload時なのかな
		// if the loaded file contains a class...
		if (class_exists($class, false))
		{
			// call the classes static init if needed
			if (static::$auto_initialize === $class)
			{
				static::$auto_initialize = null;
				if (method_exists($class, '_init') and is_callable($class.'::_init'))
				{
					call_user_func($class.'::_init');
				}
			}
		}

↑ファイルは、APPPATHのclass配下を探してるっぽかった。

SQL Buildrが組み立てるSQLを見たい

直前のクエリを見る

\DB::last_query();

sessionDriverについて

Fuel/Core/Session
 _init()
    instance() 
      forge() 
      // Session_Driverに対して操作を行う
      $driver-> new $class
      $driver->init()
      $driver->read()
// abstract
Fuel/Core/Session_Driver
※ Session_Driverを継承
Fuel/Core/Session_Memcached
constructor()
init()
read() {
  // memcachedのkeyはcookieに格納されているセッションID。ただし、encryptされるので、そのままの値じゃない。
  $key = _get_cookie
}

memcached

最後のkey一覧が素敵。ただ、localhostとは異なるホストに対してはkeyが取得できなかった。なんでだろ。 https://qiita.com/TatsuNet/items/5c89a2dbce57be28aef7

memcached-toolが使えるなら、以下でkeyとvalueが見れる。 key一覧がみたいときには工夫が必要ね。

memcached-tool localhost:11211 dump | less

memcachedのweigthについて。

https://stackoverflow.com/questions/10901480/php-memcacheaddserver-weight

memcachedの設定で、weight:100、weight:100の2台構成の設定があって、意味があるのか気になった。

怪しい結論なんだけど、weightはただの重み付けなので、上記であれば1:1の割合で分散させるってだけっぽい。 なんで合計100にしなくても、weight:30、weight:30でもいいんじゃないかなと思う。(未検証)

最初は、memcachedが片方死んでも大丈夫構成の意味かと思ったけど、少なくともfuelのmemcachedのserviceでは、addServerしたあとに、getStatsして定義したmemcachedが生きているか確認してる。1個でも死んだらエラーになる構成。 だからweight:100、weight:100で片方しんだら、50%に確率でmiss hitになるはず。

PECL

fuelPHPのバージョンを1.7から1.8にあげたとき、libsodiumをインストールしてねってログがでてた。 fuel1.8が依存してるパッケージがPHP7から実装された関数に依存してた。

どうも、PHP7より前はPHPのExtensionとして配布されてた機能がPHP7に標準搭載されたっぽい。

Fuel requires Sodium support in PHP. Either use PHP 7.2+, install the libsodium PECL extension, of the sodium-compat composer package! in /mnt/fuelphp/fuel/core/vendor/paragonie.php on 3

なので、libsodiumを別途入れなきゃいけないんだけど、remiリポジトリをみてもそれっぽいのがない。 ソースからビルドするか、PECLっていうPHPの拡張モジュールをインストールしてくれるやつを使う必要がでてきた。

PECLについては↓がとてもわかりやすそう。結局ちゃんと読んでない。

http://dqn.sakusakutto.jp/2015/07/php_extension_pecl_phpize.html

pecl search libsodiumしてからpecl install libsodiumしたんだけど、php5じゃ使えねえよっていわれっちゃった。

こういった場合、changelogを見て、指定のパッケージを落としてくるみたい。 ↓だと1系だとPHP5いけるっぽいね。 https://pecl.php.net/package-changelog.php?package=libsodium

なんだけど、この後インストールするために依存パッケージがまたでてきちゃって、そこまでして対応したくないなぁと思ったのでやめた。

yum でnothing to do になる

DNSの問題とかいろいろあるんだけど、curlしてhttpsでこけてたらこれが原因

https://qiita.com/YumaInaura/items/ef1130eb67a47fe0f2da

�POSTされたXMLオブジェクトを受け取りたい

$_POSTに全然入ってこねええってなってた。ほんで、公式をよく見ると、なんだと、、、案件。

Content-Type に application/x-www-form-urlencoded あるいは multipart/form-data を用いた

https://www.php.net/manual/ja/reserved.variables.post.php

全然わかってなかったね。

$_POSTに入ってこない形式のデータはphp://inputっていう、リクエストのbody部につながってるストリームを参照するみたい。

https://www.php.net/manual/ja/wrappers.php.php https://thisinterestsme.com/receiving-xml-via-post-php/

PHPパーフェクトガイドのメモ

関数

phpは関数を返す関数みたいなことはできないっぽい。

<?
function returnFunc() {
    return function a($value) {
        return $value + 1 ;
    }
}

変数に関数をセットしたり引数に渡したりはできるから、いわゆる関数が第1級オブジェクトってやつなのかな。 PHPでクロージャーって言葉もあるんだけど、無名関数として使えるってだけで、jsのようにインスタンスぽく使えるものを指してるんじゃないんだと思った。 callbackとして渡す分にはがんがんつかってけそう。

<?php

$array = [1,2,3];

// mapって便利だよね
$new_array = array_map(function($value){
    return $value * 2;
}, $array);

// もちろんもとの配列に影響は与えない
// 1 2 4
var_dump($array);
// 2 4 6
var_dump($new_array);

調べ物ついでにuseの記事があったのでメモ。 http://var.blog.jp/archives/75503716.html

配列

const FOOD_REPORTS = array(
    'onigiri' => array(
      'dir' => '',
      'file_pattern' => '*onigiri.txt'
    ),
    'umeboshi' => array(
      'dir' => '',
      'file_pattern' => '*ume.txt'
    ),
);


$data = array_map(function($report) {
    print_r($report);
}, FOOD_REPORTS);

// jsの感覚でmapすると、全然違うので注意
// keyが取得できない。普通にforEachつかったほうがよさそう
//Array
//(
//    [dir] => 
//    [file_pattern] => *onigiri.txt
//)
//Array
//(
//    [dir] => 
//    [file_pattern] => *ume.txt
//)

Apache関連

PHPじゃないけど、セットなことが多いのでここに書く。

allow denyとか

Apache2.4から Requireとかにかわってるから注意!詳細はまた書こう。すまんな未来の自分。

特定のメソッドを禁止したい

OPTIONSとかを禁止したい場合、Limitディレクティブとかがある。 今回は、上記を使わずにRewrite設定で対応した。

<IfModule mod_rewrite.c>
        RewriteEngine on
        # リクエストメソッドが、OPTIONSから開始される文字列の場合
        RewriteCond %{REQUEST_METHOD} ^OPTIONS
        # リライトするよ!全部を[F]にもってく。
        # Fは403
        # https://qiita.com/tsukaguitar/items/e37245260f0b1407341d
        # 一致パターン 置換パターン フラグの順に書く
        # 403に飛ばしたいだけで、置換は不要なので-にするみたい
        RewriteRule .* - [F]

Fuelのフレームワークで、.htaccsessにindex.phpにリライトする設定が書いてあるから、ここに足すことにした。 http.confとかに書いてみたけど、おそらくこにhtaccessの設定で上書きされちゃう。

FWにみる.htaccsess

Fuelのhtaccsessを抜粋するとこんなかんじ

  # リクエストされたファイルが、なかったら
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteCond %{REQUEST_FILENAME} !-d
  # index.phpに渡すよ!
	RewriteRule ^(.*)$ index.php/$1 [L]

document rootは/publicだよね。だからpublic配下にあるファイルであれば、対象にならない。

  • documentRootにないディレクトリ /cgi-binにアクセスすると403 ScriptAliasがhttpd.confに書いてある。これを消せばいい。

  • documentRootにないディレクトリ/icons にアクセスすると403が返ってくる。なにこれ。 Apacheがデフォルトで用意してるディレクトリ。↓で対策可能。 https://www.ritolab.com/entry/42

  • public配下のディレクトリにアクセスすると403 ちょっとわからん。ファイル一覧を表示させないようにしてるからかな。 https://www.atmarkit.co.jp/flinux/rensai/linuxtips/705noindexes.html

一律404にしたほうがよさげ。

Welcome back to Laravel

Eloquent

save

インスタンスに値を設定してからsaveする。

create

Eloquent->create()の引数に値を設定して保存するパターン。

リレーション

create

リレーションに対してcreateができる

# Model/Thread
# Thread has many Reply
  $this->replies()->create($reply)

この場合、作成されるリプライにthread_idが自動で乗っかる。

morphMany

これがとてもわかりやすい。

https://reffect.co.jp/laravel/perfect-understanding-polymorphic

お気に入りレコードがあって、何に対するお気に入りなのかを表現したい場合、以下のようにできる。

export  {
  favoritedRecords:[
    {
      id: 1,
      // このidはThread->idのこと
      favorited_id: 1,
      favorited_type: 'App\Thread'
    },
    {
      id: 2,
      // このidはReply->idのこと
      favorited_id: 1,
      favorited_type: 'App\Reply'
    }
  ]
}

hasManyとかだとリレーションを持てるのは1つのテーブルだけだから、ThreadFavorireRecordsとReplyFavoriteRecordsを持つことになっちゃう。

クエリをログ

最高。感謝。

https://qiita.com/ucan-lab/items/753cb9d3e4ceeb245341#comments

Eager Loading

 Thread
  Replies

こんときに、$thread->repliesは問題ない。

 Threads
  Replies

こんときに、N+1問題がでる。

リレーションの補足

リレーションにはインスタンスのプロパティとしてアクセスする。

$thread->replies

仕組みは、PHPのマジックメソッド__get()が使われている。

こういう構成になっていて、Thread->propsでアクセスすると、Modelで定義している__getが呼ばれる。

Model
  trait HasAttribute
Thread extends Model

__getが呼んでるのはこのメソッド。

public function getAttribute($key)
{
    if (!$key) {
        return;
    }

    # attributesは、SQL発行後にセットされるColumって思っていいはず
    # それがあれば当然それを返す
    if (array_key_exists($key, $this->attributes) ||
        $this->hasGetMutator($key)) {
        return $this->getAttributeValue($key);
    }

    # このself::classは、Threadではなくって、その親のModelを指す
    # traitでのselfはそういうもんなのかな
    # なので、Modelにそのpropsが定義されてればそっちを返す
    if (method_exists(self::class, $key)) {
        return;
    }

    # それ以外は、relationを返す
    return $this->getRelationValue($key);
}

getRelationValueはこんなかんじ

    protected function getRelationshipFromMethod($method)
    {
        # 結局$thread->repliesは、$thread->replies()になる
        $relation = $this->$method();

        if (!$relation instanceof Relation) {
            if (is_null($relation)) {
                throw new LogicException(sprintf(
                    '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $method
                ));
            }
            throw new LogicException(sprintf(
                '%s::%s must return a relationship instance.', static::class, $method
            ));
        }
        # が、その後$relation(HasManyインスタンス)を使って、queryを発行してるっぽい
        return tap($relation->getResults(), function ($results) use ($method) {
            $this->setRelation($method, $results);
        });
    }

プロパティで取得できるメリットってなんだろう。 モデルがデーブルと1:1で紐付いているものとした場合にプロパティで取れるとうれしいってのと同じ感じでリレーションもできるといいってことかな。

relationのメソッドに対して

  • メソッドとして実行すると、HasManyインスタンスを返してくれる。HasManyインスタンスは、クエリビルダぽく使えるので、その後にメソッドチェーンすると、クエリが発行される。

  • プロパティとしてアクセスすると、そのリレーションに関係するデータを持っているかどうかを見てくれて、すでにデータがあるのであれば、クエリを発行せずに値を返してくれる。値がないのであれば、クエリを発行してデーターをコレクションとして返してくれる。

Facade

https://qiita.com/Yorinton/items/604c5b1f3445cb23d565

  • FacadeSomethingそのものに、処理はなにもかかれていない。
  • FacadeSomethingが提供してるのは、メソッドが呼ばれたときに、そもメソッドをもクラスをインスタンス化してるだけ
  • 関数でいいんじゃねって思うのは浅はかなのだろうか。
use  XXXX\Facades\SOMETHING;

public function boot() 
{
  SOMETHING::method()
}

Facade.phpをおっかけると、ここでインスタンス化してる

    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

        # すでにresolveされているのであれば、そっちを使う
        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }

        # そうでなければ、appからとってくる
        # appは、bootstrapから渡される。Kernel.phpあたりが怪しい
        # また、appの実態であるApplicationクラスは、インスタンス[arg]の形式で、argの名前のインスタンスをmakeするってことをやってる。恐ろしい。
        # https://www.php.net/manual/ja/class.arrayaccess.php
        # https://qiita.com/metheglin/items/87e25bbdf37fbe0cd6e2

        if (static::$app) {
            return static::$resolvedInstance[$name] = static::$app[$name];
        }
    }

auth()

すごい。詳細に書いてくれてる。 https://teratail.com/questions/171582k

  • back()を使うでも書いたとおり、auth()の実態は、以下の流れで解決される
  1. auth()ヘルパ関数
  2. app()ヘルパ関数
  3. Container::make()
  4. resolve()がDIを実現してる? ↓の記事がとてもよさげなのであとでみよう。 https://blog.fagai.net/2016/09/17/laravel-dependency-injection/#toc5

解決される実態は、AuthManager.php

その次がめっちゃ厄介で、AuthManagerのインスタンスに対して存在しないメソッドを呼ぶと、magicメソッドが呼ばれるとのこと。

    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }

ほんでデフォルトのdriverのnameが返されて、resolve()へ

   public function guard($name = null)
    {
        # defaultDriverは、auth.phpのconfigを元に返す
        # defaultはsession
        $name = $name ?: $this->getDefaultDriver();

        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

resolveでcreateSessionDriverを呼ぶ!

    protected function resolve($name)
    {
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }

        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        # ここ!
        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

実態はこれ。

src/pokemon-draftingtool/vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php

gate

https://qiita.com/mpyw/items/8c5413b99b8e299f7002

特定のアクションを実行できる許可が、あるユーザーにあるかを決めるクロージャのこと → abilityと呼ぶといいかも

AuthServiceProviderで、abilityを定義する。

Gate::define('ability-name', function($user){
  // trueであれば、abilityがある。
  // booleanを返却しなきゃいけないってことではない。
  return $user->isxxx
})

ルーティングをまるっと制御したいのであれば、このGateをmiddlewareで使うことができる。

コントローラとかで?個別にGateのabilityを満たしているかチェックしたいのであれば、

allowsdeniesとかを使える。

if(Gate::allows('ability-name')){}

↑はあんまり使わなそう。ゲートはmiddlewareでやって、コントローラの中はPolicyを使えばいいかも?

middlewareはデフォルトで、Kernel.phpに定義されてる。

    protected $routeMiddleware = [
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
    ];

middlewareを使う場合の書き方は、 https://readouble.com/laravel/6.x/ja/middleware.html

複数ルートにmiddlewareを適用させる。

// これcanグループを適用させるようにも見えるな、、、
//https://readouble.com/laravel/6.x/ja/routing.html
// Route:middlewareのほうがただしい?
// groupの最初の引数に共通の属性を渡せるとは書いてある。
Route::group(['middleware' => ['can']], function () {
    //
});

次が、大事。middlewareのhandleにパラメータを渡したい場合は、こう書く。 p

Route::put('post/{id}', function ($id) {
    // middleware名:パラメータ
})->middleware('role:editor');

なので、以下は、canミドルウェアに引数create-groupsを渡すってことね!

  Route::group(['middleware' => 'can:create-groups'], function () {
      Route::get('/create', 'PostsController@create');
      Route::post('/', 'PostsController@store');
  });

で、Authorizer.phpのmiddlewareはこんなかんじ。

    public function handle($request, Closure $next, $ability, ...$models)
    {
        // こいつがメイン
        $this->gate->authorize($ability, $this->getGateArguments($request, $models));

        return $next($request);
    }

Gateの正体は、Illuminate\Auth\Access\Gateで、Illuminate\Auth\AuthServiceProviderでbindしてる。

    protected function registerAccessGate()
    {
        $this->app->singleton(GateContract::class, function ($app) {
            return new Gate($app, function () use ($app) {
                return call_user_func($app['auth']->userResolver());
            });
        });
    }

こいつまでの流れは以下の記事がわかりやすい。Facadeの仕組みで前もみたかも。 https://qiita.com/Yorinton/items/604c5b1f3445cb23d565

あとは、Gateをみると、authorizeはこんなかんじ。

    public function authorize($ability, $arguments = [])
    {
        return $this->inspect($ability, $arguments)->authorize();
    }

    public function inspect($ability, $arguments = [])
    {
        try {
            // このさきは気が向いたら
            // おそらくClosureが実行結果がresult
            $result = $this->raw($ability, $arguments);

            // resultにはboolean以外も返せる
            if ($result instanceof Response) {
                return $result;
            }

            // denyをみれば、例外をスローすることがわかる
            return $result ? Response::allow() : Response::deny();
        } catch (AuthorizationException $e) {
            return $e->toResponse();
        }
    }

SessionとかCookieとか

EncryptCookies

web.phpのMiddlewareに適用されている、EncryptCookiesでCookieを復号してる。ブラウザでCookieのlaravel_sessionは暗号化されるので注意。 復号化すると、sessionのkey名が入ってたりする。

    public function handle($request, Closure $next)
    {
        return $this->encrypt($next($this->decrypt($request)));
    }

StartSession

セッションを開始するMiddleware。

Cookieのlaravel_sessionからセッションのkeyとなるものを取得する。StoreクラスにsetIdしてる。

    public function getSession(Request $request)
    {
        return tap($this->manager->driver(), function ($session) use ($request) {
            $session->setId($request->cookies->get($session->getName()));
        });
    }

その後、

    protected function startSession(Request $request)
    {
        return tap($this->getSession($request), function ($session) use ($request) {
            $session->setRequestOnHandler($request);

            // こいつで、setIdしたidをもとに、セッションを新規に作成or復元してるとおも。
            $session->start();
        });
    }

VerifyCsrfToken

CSRF対策。セッションとCookieにXSRF-TOKENを書かれてる。 この値を、VerifyCsrfTokenで比較する。

Ajaxの場合、X-XSRF-TOKENヘッダーにセットする、 このへん。

    protected function getTokenFromRequest($request)
    {
        $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

        if (!$token && $header = $request->header('X-XSRF-TOKEN')) {
            $token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
        }

        return $token;
    }

READ系のメソッドの場合、tokenの比較はしない。

テスココードのbe()

    public function be(UserContract $user, $driver = null)
    {
        if (isset($user->wasRecentlyCreated) && $user->wasRecentlyCreated) {
            $user->wasRecentlyCreated = false;
        }

        # SessionGuardのsetUser、SessionGuardに認証済Userとしてつっこめる
        $this->app['auth']->guard($driver)->setUser($user);

        # AuthManager->shouldUse これはなんで必要なんだろ
        $this->app['auth']->shouldUse($driver);

        return $this;
    }

その他

バリデーション

https://qiita.com/cyrt/items/2d90677dc920cd2e5459 DIについて https://qiita.com/kd9951/items/951ab700f28d1d49c9c9

FormRequestServiceProvider

    // bootはサービスコンテナにregisterした後に、インスタンス化したオブジェクトに対して行う操作
    public function boot()
    {
        // ValidatesWhenResolved IFを実装しているインスタンスに対して
        // validateResolved()を実行する
        // この$resolvedは、FormRequestを継承した、CreateRequestとか
        $this->app->afterResolving(ValidatesWhenResolved::class, function ($resolved) {
            $resolved->validateResolved();
        });

        $this->app->resolving(FormRequest::class, function ($request, $app) {
            $request = FormRequest::createFrom($app['request'], $request);

            $request->setContainer($app)->setRedirector($app->make(Redirector::class));
        });
    }

で、validateResolvedはこいつ。

trait ValidatesWhenResolvedTrait
{
    /**
     * Validate the class instance.
     *
     * @return void
     */
    public function validateResolved()
    {
        $this->prepareForValidation();

        if (!$this->passesAuthorization()) {
            $this->failedAuthorization();
        }

        // これは、Illuminate\Validation\Validator 
        $instance = $this->getValidatorInstance();

        if ($instance->fails()) {
            $this->failedValidation($instance);
        }

        $this->passedValidation();
    }

back()を追う

  • back()の実態はヘルパー関数

※ ヘルパー関数は、composer.lockをみるとfilesの定義で書いてあるので、autoload後であればどっからでも参照できる。

    function back($status = 302, $headers = [], $fallback = false)
    {
        return app('redirect')->back($status, $headers, $fallback);
    }
}
  • app()関数もヘルパー関数
    function app($abstract = null, array $parameters = [])
    {
        if (is_null($abstract)) {
            return Container::getInstance();
        }

        # make()を追うと、resolve()につながる。resolveはごにょごにょしてargの関係するインスタンスを返してる
        #src/pokemon-draftingtool/vendor/laravel/framework/src/Illuminate/Foundation/Application.php 
        # ↑に、クラスのaliasがきってある。
        # redirectの実態は、\Illuminate\Routing\Redirector
        return Container::getInstance()->make($abstract, $parameters);
    }

つまり、back()はRedirector()->back() 中を見ると、リファラーをみてた。納得。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment