- 投稿日:2021-03-02T23:40:46+09:00
【MAMP環境】新アプリ作成コマンド・初期設定
◆ MAMP「新アプリ作成・初期設定」記事作成の目的
完全に私のメモです。忘れないように記述しました!
MAMP環境でアプリを作ったことがあるけど、毎回コマンド忘れるんだよね〜
という方には便利に使っていただけるかも?◆ アプリケーションを作成する
1)composerインストール済みの場合
$ cd /Applications/MAMP/htdocs/ $ composer create-project laravel/laravel --prefer-dist test1 **アプリできる** $ cd test1 $ chmod -R 777 storage $ chmod -R 777 bootstrap/cache ※ -R ディレクトリ内の複数のファイルも変更対象とする2) composerのインストール未の場合
$ cd /Applications/MAMP/htdocs/ $ curl -sS https://getcomposer.org/installer | php $ sudo mv composer.phar /usr/local/bin/composer $ composer -V $ composer create-project laravel/laravel --prefer-dist blog $ cd blog/ $ chmod -R 777 storage $ chmod -R 777 bootstrap/cache◆ 初期設定(アプリ作成後)
1)config/app.php
'timezone' => ‘UTC’ 'timezone' => ‘Asia/Tokyo’ 'locale' => ‘en’ ‘locale' => ‘ja’ 'faker_locale' => 'en_US’, 'faker_locale' => ‘ja_JP’2).env
APP_NAME=Laravel APP_NAME=Name ※MAMPの設定(公式サイトにあり) DB_PORT=3306 DB_PORT=8889 DB_DATABASE=laravel DB_DATABASE=test1 DB_PASSWORD= DB_PASSWORD=root ※追記 DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock3)app/Providers/
※mysqlの文字コードが対応していない時に必要 ※2箇所追記 use Illuminate\Support\Facades\Schema; public function boot() { Schema::defaultStringLength(191); }以上!
- 投稿日:2021-03-02T21:56:06+09:00
PHPの多次元配列を特定の値でソート(array_multisort関数)
単一の配列のソートはPHPマニュアルの配列のソートを参考にすればできますが、多次元配列の場合のソートは複雑…
というわけで、いつでも確認できるように記録しておきます。多次元配列の例
次のような2次元配列のデータがあるとします。
$records = [ [ 'ID' => "A00004", 'PAYMENTNO' => "11111", 'PRICE' => "7500", 'ITEMNAME' => "スニーカー" ], [ 'ID' => "A00002", 'PAYMENTNO' => "22222", 'PRICE' => "4500", 'ITEMNAME' => "カットソー" ], [ 'ID' => "A00001", 'PAYMENTNO' => "05555", 'PRICE' => "5000", 'ITEMNAME' => "ニット" ], [ 'ID' => "A00003", 'PAYMENTNO' => "03333", 'PRICE' => "7000", 'ITEMNAME' => "ジーンズ" ], [ 'ID' => "A00003", 'PAYMENTNO' => "03331", 'PRICE' => "9000", 'ITEMNAME' => "ジャケット" ] ];この配列に対して、「ID」の昇順で並べ、「ID」の値が同じ場合は「PAYMENTNO」の昇順で並べたいとします。
この場合、array_multisort関数とarray_column関数を組み合わせて使えばスッキリ実装できます。ソート実行
// array_multisort(ソートの軸となる配列(1), ソートの軸となる配列(2), ソートしたい配列) array_multisort(array_column($records, 'ID'), array_column($records, 'PAYMENTNO'), $records);ここではソートの軸を2つにしていますが、もちろん1つでも良いですし、引数を追加すれば3つでも4つでも増やせます。
省略せずに書くなら
array_multisort(ソートの軸となる配列(1), ソート順(1), ソート方法(1), ソートの軸となる配列(2), ソート順(2), ソート方法(2), ソートしたい配列)
となりますが、以下2つはデフォルトの設定を使うのであれば省略可能です。
- ソート順:デフォルトがSORT_ASC
- ソート方法:デフォルトがSORT_REGULAR
※設定の詳細は公式リファレンス参照
この「ソートの軸となる配列」は、$recordsをforeachで回してIDを取り出して、配列を作成して…としても良いのですが、array_columnを使えば配列から特定のキーに対する要素を1発で取り出せるので、この方が楽でコードもスッキリします。
array_column関数ソート後
上でソートした配列
$records
をvar_dumpしてみると、以下のようになります。array(5) { [0]=> array(4) { ["ID"]=> string(6) "A00001" ["PAYMENTNO"]=> string(5) "05555" ["PRICE"]=> string(4) "5000" ["ITEMNAME"]=> string(9) "ニット" } [1]=> array(4) { ["ID"]=> string(6) "A00002" ["PAYMENTNO"]=> string(5) "22222" ["PRICE"]=> string(4) "4500" ["ITEMNAME"]=> string(15) "カットソー" } [2]=> array(4) { ["ID"]=> string(6) "A00003" ["PAYMENTNO"]=> string(5) "03331" ["PRICE"]=> string(4) "9000" ["ITEMNAME"]=> string(15) "ジャケット" } [3]=> array(4) { ["ID"]=> string(6) "A00003" ["PAYMENTNO"]=> string(5) "03333" ["PRICE"]=> string(4) "7000" ["ITEMNAME"]=> string(12) "ジーンズ" } [4]=> array(4) { ["ID"]=> string(6) "A00004" ["PAYMENTNO"]=> string(5) "11111" ["PRICE"]=> string(4) "7500" ["ITEMNAME"]=> string(15) "スニーカー" } }ちゃんと「ID」の昇順かつ、「ID」が同じ場合は「PAYMENTNO」の昇順になっていますね!
参考にした記事
- 投稿日:2021-03-02T18:38:39+09:00
line-bot-sdkをインストールしたらsocketsがないって言われた
環境・前提
筆者は素人です。
この記事は記録が目的です。
ただ、流れくらいは参考になって欲しい。windows10homeにdocker-composeでLEMP環境を作り、Laravelのプロジェクトを作成。
LaravelでLINE Botを作りたいのでline-bot-sdkを入れたかった。やったこと
dockerのコンテナで
$ composer require linecorp/line-bot-sdk 4.2.*
を実行。エラーの内容
Problem 1 - Installation request for linecorp/line-bot-sdk 4.2.* -> satisfiable by linecorp/line-bot-sdk[4.2.0]. - linecorp/line-bot-sdk 4.2.0 requires ext-sockets * -> the requested PHP extension sockets is missing from your system.要はext-socketsというやつがないからエラーになるっぽい。
解決策を調べた
ググったら2つのやり方が出てきた。
1つはphp.iniファイルにある
;extension=sockets
のコメントアウトを外す、というもの。
でも自分のphp.iniにはそもそも;extension=sockets
自体がなかった。2つ目はDockerfileの
RUN docker-php-ext-installに
sockets
を加えるというもの。で、
php -m
コマンドを実行してインストールされているモジュールを確認したところ、
socketのモジュールがなかったので2つ目のやり方でいけると予想。解決方法
2つ目のやり方で
Dockerfile
に変更を加え、コンテナから出て
$docker-compose down
$docker-compose up -d --build
を実行。
これでDockerfile
の再読み込みを行う。結果
無事いけました!
- 投稿日:2021-03-02T17:07:25+09:00
PHPで簡単なAPIを作った話
今回作るもの
こんな感じのGETパレメーターを指定すれば足し算して返してくれるコードです
https://example.com/api.php?front=1&back=2レスポンス
{"status":"true","result":3,"formula":"1+2"}API化するメリットはマジでなさそうですが、簡単に作れるので今回はこれで解説していきます
コード
一旦すべてのコードを置いておきます(コピペならここをお使いください)
一応 githubにも同じものをアップしていますapi.php<?php $front=$_GET['front']; $back=$_GET['back']; $result=$front+$back; $response['result']=$result; $response['status']="true"; $response['formula']=$front.'+'.$back; print json_encode($response, JSON_UNESCAPED_UNICODE);とっても簡単なコードです。
GETパラメーターから数字を抜き出し
↓
足し算して
↓
表示する
ただこれだけ。APIと聞くと難しそうですが基礎的な仕組み自体は簡単です解説
一つずつ解説していきます(といっても7行しかありませんが)
$front=$_GET['front']; $back=$_GET['back'];GETパラメーターから指定した値を取得するコードです。今回は
front
というキーの値を$front
という変数に、back
というキーの値を$back
という変数に代入しています。$result=$front+$back;ただの足し算です。
$front
と$back
に入っている数字を足して$result
という変数に代入します。$response['result']=$result;このコードは
$result
に入っている計算結果を$response
という配列に代入します。
今回はキーにresultを指定しました。$response['status']="true";APIを使う側がレスポンスが返ってきたか確認しやすいようにするために記述しておいた方がいいでしょう。
無くても当然動きますが。$response['formula']=$front.'+'.$back;計算式を作り、配列である
$response
のformulaキーに代入します。
javascriptなどの言語とは違って文字列をつなげるときは.
を使わないといけないのは注意ポイントですね。
たまーにやらかしてエラーが出ちゃいます(笑)print json_encode($response, JSON_UNESCAPED_UNICODE);最後に出力します。
echo
を使っても
'''json_encode'''でjson化します。その際日本語が含まれていると勝手にエスケープしてしまうのでJSON_UNESCAPED_UNICODE
を指定しましょう。
今回はなくても問題ないですが、、、最小構成
「別に私は足し算するAPIなんか作りたいわけじゃないよ!!」
という人のためにできるだけ最小構成にしたものを最後に載せておきます。
APIを作る際にコピペかなんかで使ってくださいapi.php<?php $response['status']="true"; print json_encode($response);では楽しいphpライフ(?)を~
- 投稿日:2021-03-02T15:08:30+09:00
jQueryで上からslideToggleでメッセージを出す
はじめに
slideToggleを使ってニュルっとメッセージを出す方法の記録です。
jQueryのコード
以下のコードでメッセージの出す、戻すを行う。
表示するメッセージを受け取った場合は空白を全て取り除く。
そしてslideToggleで表示し、5秒後にslideToggleで戻す。let $slideMsg = $('#js-slide-msg'); let msg = $slideMsg.text(); if(msg.replace(/\s+/g, "")){ $jsShowMsg.slideToggle().show(); setTimeout(function(){ $jsShowMsg.slideToggle().show(); }, 5000); }表示について
$msgに表示するメッセージが入っていれば表示させる。
<div id="js-slide-msg"> <?php if(!empty($msg) echo $msg; ?> </div>
- 投稿日:2021-03-02T12:31:48+09:00
PHP5.4からS3への画像アップロード
概要
- PHP環境からS3への画像アップロードできるようにするため、 aws-sdk-phpを使って試してみた。
- aws-sdk-phpバージョン3がPHP5.5以降のみ対応のため、SDKのバージョンを2に変更して再度実施。
- S3バケットの作成とIAMからユーザー作成を実施。
- vagrantの開発環境からS3へのアップするサンプル内容を記載。
環境
Mac Big Sur 11.2.1
vagrant 2.2.7
cake php 1系
php 5.4.451回目のaws-sdk-phpインストール(失敗)
vagrant sshでアクセスして実行。失敗する。
$ composer require aws/aws-sdk-php zsh: correct composer to _composer [nyae]? zsh: command not found: composerzshの問題っぽいので、こちらのサイトを参考にzshrcファイルに情報をセットする。
$ vim ~/.zshrc.zshrcファイル内に以下の文字を設定して保存。
export PATH="$HOME/.composer/vendor/bin:$PATH"設定の反映をする。
$ source ~/.zshrc2回目のaws-sdk-phpインストール(失敗)
$ composer require aws/aws-sdk-php 今度は以下のエラー Could not open input file: /usr/local/bin/composer.pharcomposerが入っていないと思わるので、インストールする。
Vagrant環境内でcomposerをインストール
[vagrant@dev bin]$ pwd /usr/local/bin # インストールコマンド curl -sS https://getcomposer.org/installer | php curl: (35) error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version→TLS1.2問題っぽい
phpコマンドを使ってインストール
$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" $ ls -la : -rw-r--r-- 1 xxxxx 1451310402 58460 Mar 1 15:33 composer-setup.php : : $ php composer-setup.php All settings correct for using Composer Downloading... Composer (version 2.0.11) successfully installed to: /Users/.../composer.phar Use it: php composer.phar $ php -r "unlink('composer-setup.php');" $ ls -la : : -rwxr-xr-x 1 xxxx 1451310402 2210024 Mar 1 15:34 composer.phar :移動
$ mv composer.phar /usr/local/bin/composer $ composer ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ /_/ Composer version 2.0.11 2021-02-24 14:57:23 Usage: : : :3回目のaws-sdk-phpインストール(成功)
$ composer require aws/aws-sdk-php Using version ^3.173 for aws/aws-sdk-php ./composer.json has been created Running composer update aws/aws-sdk-php Loading composer repositories with package information Updating dependencies Lock file operations: 9 installs, 0 updates, 0 removals - Locking aws/aws-sdk-php (3.173.18) - Locking guzzlehttp/guzzle (7.2.0) - Locking guzzlehttp/promises (1.4.0) - Locking guzzlehttp/psr7 (1.7.0) - Locking mtdowling/jmespath.php (2.6.0) - Locking psr/http-client (1.0.1) - Locking psr/http-message (1.0.1) - Locking ralouphie/getallheaders (3.0.3) - Locking symfony/polyfill-mbstring (v1.22.1) Writing lock file Installing dependencies from lock file (including require-dev) Package operations: 9 installs, 0 updates, 0 removals - Downloading symfony/polyfill-mbstring (v1.22.1) - Downloading mtdowling/jmespath.php (2.6.0) - Downloading ralouphie/getallheaders (3.0.3) - Downloading psr/http-message (1.0.1) - Downloading guzzlehttp/psr7 (1.7.0) - Downloading guzzlehttp/promises (1.4.0) - Downloading psr/http-client (1.0.1) - Downloading guzzlehttp/guzzle (7.2.0) - Downloading aws/aws-sdk-php (3.173.18) - Installing symfony/polyfill-mbstring (v1.22.1): Extracting archive - Installing mtdowling/jmespath.php (2.6.0): Extracting archive - Installing ralouphie/getallheaders (3.0.3): Extracting archive - Installing psr/http-message (1.0.1): Extracting archive - Installing guzzlehttp/psr7 (1.7.0): Extracting archive - Installing guzzlehttp/promises (1.4.0): Extracting archive - Installing psr/http-client (1.0.1): Extracting archive - Installing guzzlehttp/guzzle (7.2.0): Extracting archive - Installing aws/aws-sdk-php (3.173.18): Extracting archive 5 package suggestions were added by new dependencies, use `composer suggest` to see details. Generating autoload files 2 packages you are using are looking for funding. Use the `composer fund` command to find out more!ここでaws-sdk-phpバージョン3の対応PHPバージョンが5.5以降だと気づく。
SDKのバージョンを2にしてPHPバージョンを指定してみる
まずは、PHPバージョンを指定する。
$ composer config platform.php 5.4.45composer.jsonの中身を変更する。aws-sdk-phpを2にする。
{ "require": { "aws/aws-sdk-php": "^2.8" }, "config": { "platform": { "php": "5.4.45" } } }再度composerセットアップする。
$ php composer-setup.phpこれで、PHPを側の環境は整ったはず。
ディレクトリ構成
project_root |_app |_cake : : |_vendor |_composer.json |_composer.lockAWS S3作成とS3利用するIAMユーザー作成
S3バケット作成
- ログイン画面→サービス→S3と検索
- S3のダッシュボードが表示されるので、「バケットを作成」を選択
- バケット名を設定
- リージョンはデフォルト(ここではap-northeast-1)
- パブリックアクセスをすべて ブロックのチェックを外す
- これをすると全公開となるので注意が必要
- 内容を確認して 「バケットを作成」ボタンを押す
S3アクセス用のIAMを作成
- ログイン画面→サービス→IAMと検索
ユーザー情報を追加して、「次のステップ:アクセス権限」を選択
アクセス許可の設定で、既存のポリシーを直接アタッチを選択
タグの追加(ここは一旦何も設定しないで「次のステップ:確認」を選択)
ユーザーの追加完了後、アクセスキーとシークレットアクセスキーが発行
AWS S3への画像アップロード
PHPで以下の記述をして実行した。
(下記ソースは開発中のサービスにあるConstroller内で記載して実行)<?php require 'vendor/autoload.php'; use Aws\S3\S3Client; use Aws\Common\Enum\Region; use Aws\S3\Exception\S3Exception; class xxxx { public function 'xxxxメソッド名'() $bucket = 'awsで設定したバケット名'; $aws_account = array( 'key' => 'ユーザー作成時のアクセスキー', 'secret' => 'ユーザー作成時のシークレットアクセスキー'); $this->s3client = S3Client::factory($aws_account); $this->s3client->registerStreamWrapper(); $sourcefile = 'アップ元のファイルフルパス'; $filename = 'xxxx.jpg'; try { $this->s3client->putObject(array( 'Bucket' => $bucket, 'Key' => 'アップ先のディレクトリ名'.$filename, 'ACL' => 'public-read', 'SourceFile' => $sourcefile, )); } catch (S3Exception $exc) { echo "Upload NG"; echo $exc->getMessage(); var_dump($exc); return false; } var_dump("DONE"); return true; } } ?>
- 投稿日:2021-03-02T12:14:59+09:00
【Composer】オートロードの仕組みを追う
初めに
Laravelを使っている方ならこんな疑問を抱いたことがあるんじゃないでしょうか。
なんで
\App\User
とか\Illuminate\Hogehoge
って書くだけでApp/User.php
のUserクラスとかvendor/laravel/framework/src/Illuminate/Hogehoge.php
のHogeHogeクラスが使えるようになってんの?通常、外部のクラスを使うときにはrequireなどを使って該当のファイルをインポートしなければならないはずです。
今回はそんな疑問と密接に関わるComposerのオートロードについて調べていき、提示した疑問を一緒に解決していきましょう。
実行環境
- PHP
7.3.4
- Composer
2.0.11
- Laravel
6.20.16
そもそもオートロードとは
オートロードの仕組みについて追っていく前に、そもそもオートロードって何をしているのでしょうか。
結論を言いますと、単純に
require
やinclude
を使ってソースコードを1つ1つインポートしているだけです。ただし、
require "/path/to/vendor/laravel/src/framework/Illuminate/Foo/Hogehoge1.php"; require "/path/to/vendor/laravel/src/framework/Illuminate/Foo/Hogehoge2.php"; // ... require "/path/to/vendor/laravel/src/framework/Illuminate/Foo/Hogehoge10.php";のように、べた書きで1つ1つソースコードをインポートしているわけではありません。
ちゃんとPHPの仕組みを利用して自動でソースコードを読み込む機能を構築しています。
PHPでは通常、外部のファイルに記述されているクラスなどを利用するときは、
require "path/to/Foo.php" require "path/to/Bar.php"というように、読み込みたいファイルを絶対パスもしくは自ファイルからの相対パスで
require
もしくはinclude
しなければなりません。もしもこれを行わないまま外部ファイルに書かれているクラスを呼びだそうとしても、
Uncaught Error: Call to undefined class ...というようにエラーが出力され、クラスを呼び出すことができません。
このようにオートロードをせずとも、自身で
require
することで外部ファイルを読み込むことはできます。しかし、この方法では扱うファイル数が増えてくるにつれて以下のようなデメリットが生じてきます。
require
するためには呼び出したいクラスの定義されているソースコードへのパスをあらかじめ知っていなければならない- 必要なクラスが5個や6個となってくるとさすがに記述するのが煩わしいし、typoも発生しやすくなる。
- パッケージの更新などによりディレクトリ構造が変更された日には、改めて
require
するソースコードのパスを正しいものに書き直さなければならない想像しただけで萎えてきます。
人の手でソースコードの所在を管理することが如何に面倒で非効率的であるか分かるでしょうか。
こういった面倒なインポート作業を、Composerのオートロード機能が代わりに担当しています。
そのおかげで、私たちはクラスの所在などを一切気にせずビジネスロジックの開発に注力することができるのですね。
オートロード処理の起点:vendor/autoload.php
オートロードがなにをしているかが分かったところで、ではどのようにオートロードが行われているをソースコードを追いながら見てみましょう。
ここではLaravelを使っているものだと仮定します。
まずはLaravelアプリのエントリポイントとなる
public/index.php
を見てみます。public/index.php<?php define('LARAVEL_START', microtime(true)); require __DIR__.'/../vendor/autoload.php'; $app = require_once __DIR__.'/../bootstrap/app.php'; $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); $response->send(); $kernel->terminate($request, $response);コメントなどを省くと、Laravelによる一連の処理はここに書かれていることがすべてです。Laravelの豊富な機能がこのかなり短いコードを起点に提供されているんですね。
この中の2番目の処理を見てください。
public/index.phprequire __DIR__.'/../vendor/autoload.php';名前から見ていかにもオートロードしてそうなファイルを
require
してますね。このファイルを見てみましょう。vendor/autoload.php<?php require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInitcde23787628405c61112bd11321f024e::getLoader();
ComposerAutoloaderInitcde23787628405c61112bd11321f024e
の部分は人によって異なっているのだと思います。
public/index.php
に続きかなり短いコード量です。ここでは
ComposerAutoloaderInitcde23787628405c61112bd11321f024e
という謎のクラスで定義されているgetLoader()
とやらクラスメソッドが実行されているだけです。
vendor/autoload.php
の行っている処理がこれだけなので、おそらく::getLoader()
メソッドの中にどうやってオートロード機能を提供しているかの答えがありそうです。このクラスの実装は一つ手前の処理で読み込まれている、
vendor/composer/autoload_real.php
にありそうです。では、
autoload_real.php
を見てみましょう。オートロード機能の中核:vendor/composer/autoload_real.php
vendor/autoload.php
でrequire_once
されていたvendor/composer/autoload_real.php
を見てみると、
確かに先ほどの謎のクラスComposerAutoloaderInitcde23787628405c61112bd11321f024e
が実装されていました。vendor/composer/autoload_real.php// ① class ComposerAutoloaderInitcde23787628405c61112bd11321f024e { private static $loader; public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } } public static function getLoader() { if (null !== self::$loader) { return self::$loader; } require __DIR__ . '/platform_check.php'; spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } } $loader->register(true); if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequirecde23787628405c61112bd11321f024e($fileIdentifier, $file); } return $loader; } } // ② function composerRequirecde23787628405c61112bd11321f024e($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } }このファイルには①で示した
ComposerAutoloaderInitcde23787628405c61112bd11321f024e
クラスと、②で示したcomposerRequirecde23787628405c61112bd11321f024e()
関数が定義されているようです。クラスのほうにはさらに、静的プロパティである
$loader
と静的メソッドであるloadClassLoader()
とgetLoader()
が実装されています。
vendor/autoload.php
で呼び出されているのは::getLoader()
の方なのでそちらに注目してみましょう。オートロード機能の実態を生成:getLoader()
以下、
getLoader()
部分のみの抜粋です。vendor/composer/autoload_real.phppublic static function getLoader() { if (null !== self::$loader) { // ① return self::$loader; } require __DIR__ . '/platform_check.php'; // ② // ③ spl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader')); // ④ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { // ⑤ require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::getInitializer($loader)); } else { // ⑤´ $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } } // ⑥ $loader->register(true); // ⑦ if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequirecde23787628405c61112bd11321f024e($fileIdentifier, $file); } return $loader; }ここから一つ一つ処理を追っているのでとても長くなります。耐えつつ読み進めていただければと思います。
①の処理:$loaderの有無を確認
①を見てください。
vendor/composer/autoload_real.phpif (null !== self::$loader) { // ① return self::$loader; }クラスプロパティ、
self::$loader
がnull
でなければ$loader
を返すという分岐を行っています。
vendor/autoload.php
でgetLoader()
が実行されたときにはまだ$loader
には何も代入されていないので、この条件分岐ではなにもしません。②の処理:動作環境のチェック
②を見てみましょう。
vendor/composer/autoload_real.phprequire __DIR__ . '/platform_check.php'; // ②名前から鑑みに、実行環境のチェックを行いそうなファイルが
require
されていますね。このファイルの処理は本筋からそれるので、コードの記載とコメントによる簡単な説明にとどめておきます。
vendor/composer/platform_check.php.php<?php $issues = array(); // 問題があった時にその情報を保存する配列を保存する配列 if (!(PHP_VERSION_ID >= 70205)) { // PHPのバージョンが7.2.5より下だった場合 $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". You are running ' . PHP_VERSION . '.'; } // 上記の問題が保存されていた場合 if ($issues) { if (!headers_sent()) { // まだヘッダが送信されていない場合 header('HTTP/1.1 500 Internal Server Error'); // ステータスコード500のヘッダを作成 } if (!ini_get('display_errors')) { // php.iniにdisplay_errorsの項目がないか、またはあってもOffであるか if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { // 使用しているPHPのインターフェースがcliもしくはphpdbgか fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); //STDERRに問題の情報を出力する } elseif (!headers_sent()) { // まだヘッダが送信されていない場合 echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); // 自身の使っているPHPバージョンの情報を省いて出力する } } // エラーを引き起こして、エラーメッセージを通知する。 trigger_error( 'Composer detected issues in your platform: ' . implode(' ', $issues), E_USER_ERROR ); }③:spl_autoload_register()で未定義クラス呼び出し時の処理を登録
③の処理は、Composerがオートロード機能を構築する上で重要な関数を使っている部分なので、重点的に解説していきます。
まずはコードの抜粋です
vendor/composer/autoload_real.phpspl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'));1行目
vendor/composer/autoload_real.phpspl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);この
spl_autoload_register()
という関数は何をしているのでしょうか。以前私が投稿した記事でも解説したのですが、この関数は
未定義のクラスやインターフェースが読み込まれた際に、実行してほしい処理を登録しておくことができる関数
と解釈すればおおむね問題ないと思います。
通常、
new HogeHoge();のように呼び出さしたクラスが自ファイルや
require
したファイルに定義されていないものだった場合、PHP Error: Class 'Hogehoge' not found in ...とエラーが出力されてその場で処理が終了するのが普通です。
ここで、
spl_autoload_register(function($class) { echo "Want to load $class.\n"; require __DIR__ . "/${class}.php"; }) new HogeHoge();のような例で、
spl_autoload_register()
の第1引数に、未定義クラスが呼び出された時の処理を登録してみましょう。このソースコードを実行すると、
new HogeHoge()
の部分でエラーが起きる前に、Want to load <呼び出したクラス名>.\n
と出力されます。即座にエラーが出力されなかったので、確かに未定義クラス呼び出し時の処理が登録できているようです。
そして、
echo
の内容が出力された後、同一ディレクトリ内の$class.php
($class
には未定義クラスのクラス名が自動で代入されます)があればそれをrequire
しようと試みます。この場合ですと、同一ディレクトリ内に
HogeHoge.php
があればrequire
します。もし
HogeHoge.php
にHogeHoge
クラスが存在すればそれを呼び出して、new HogeHoge()
でインスタンスを生成します。試しに同一ディレクトリ上にHogeHoge.phpという名前のファイルを作成し、その中で
HogeHoge
クラスを定義してください。その後でnew HogeHoge()の処理があるソースコードを実行してみてください。
先ほどまでは
new HogeHoge()
でエラーが出力されたのに対して、spl_autoload_register()
を追加した後ではエラーも何も起こらなくなったはずです。これはつまり、
HogeHoge()
が正しくインスタンス化されたことを意味します。
sql_autoload_register()
の動きが分かったところで改めてソースコードの1行目を見てみましょう。vendor/composer/autoload_real.phpspl_autoload_register(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'), true, true);
spl_autoload_register()
の第1引数には、登録するオートロード関数を配列として指定することもできます。今回の場合ですと、
ComposerAutoloaderInitcde23787628405c61112bd11321f024e
クラス(つまり自身のクラスですね)のloadClassLoader
メソッドをオートロード関数として登録するという処理になります。この
loadClassLoader()
メソッドは自クラスに実装されていた2つのクラスメソッドのうちのもう1つですね。(1つはgetLoader()
)第2、第3引数の意味はそれぞれ
- 第2引数
- 第1引数のオートロード関数が登録できなかった場合に例外をthrowするか
- 第3引数
- すでに別のオートロード関数が登録されている場合、それらより優先して第1引数のオートロード関数を実行させるか
となっています。
以上のことから、1行目の処理を説明しますと、
ComposerAutoloaderInitcde23787628405c61112bd11321f024e::loadClassLoader
をオートロード関数として最優先に登録する。もし登録できなかったら例外をthrow
する。といった処理になります。
ではこの
loadClassLoader()
メソッドについて見る、その前に次の行の説明をしておいたほうがloadClassLoader()
メソッドについても理解しやすいと思います。2行目
vendor/composer/autoload_real.phpself::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
self::$loader
とローカルスコープの$loader
に\Composer\Autoload\ClassLoader
クラスのインスタンスを代入しています。ですが
\Composer\Autoload\ClassLoader
はこの時点では未定義です。なので、通常ではここでエラーが出力され処理が止まるのですが、先ほど
spl_autoload_register()
でオートロード関数としてloadClassLoader()
を登録しておきましたね。なので、エラーが出力される前にそのメソッドが実行されます。
では改めて、
loadClassLoader()
メソッドを見てみましょう。vendor/composer/autoload_real.phppublic static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } }中身を見てわかるように、やはり
\Composer\Autoload\ClassLoader
の実装ファイルであろうvendor/composer/ClassLoader.php
をrequire
してましたね。詳しく処理を見てみると、呼び出された未定義クラスが
\Composer\Autoload\ClassLoader
であれば、同一ディレクトリ上のClassLoader.php
をrequire
する。という処理になっています。
vendor/composer/ClassLoader.php
について、現在必要な部分のみを見てみましょう。vendor/composer/ClassLoader.phpnamespace Composer\Autoload; class ClassLoader { private $vendorDir; // ~~中略~~ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; } // ~~後略~~ }やはり、
\Composer\Autoload\ClassLoader
クラスが実装されていましたね。
__construct()
でパラメータに受け取ったパスをprivate $vendorDir
に代入しているようです。
\Composer\Autoload\ClassLoader
クラスについては今はこの程度の理解で十分です。改めて2行目の処理を見てみましょう。
(以下、該当部分を再掲)
vendor/composer/autoload_real.phpself::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
\dirname(\dirname(__FILE__))
は内側から
__FILE__
/path/to/vendor/composer/autoload_real.php
\dirname(__FILE__)
/path/to/vendor/composer
\dirname(\dirname(__FILE__))
/path/to/vendor
と、解決されます。
ですので、
new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
によって、
$vendorDir
プロパティに'/path/to/vendor'
が代入されたClassLoader
インスタンスをself::$loader
と$loader
に代入する。というのが2行目で行っている処理になります。
ここで代入された
\Composer\Autoload\ClassLoader
インスタンスが、Composerのオートロードを実現する上での核となるインスタンスになります。詳細については後ほど(⑥あたりで)説明します。
3行目
vendor/composer/autoload_real.phpspl_autoload_unregister(array('ComposerAutoloaderInitcde23787628405c61112bd11321f024e', 'loadClassLoader'));これは関数名からなんとなく察せるんじゃないでしょうか。
ここでは、
spl_autoload_register()
の反対、オートロード関数の登録解除を行っています。具体的には、先ほどの
loadClassLoader()
メソッドをオートロード関数から削除しています。③のまとめ
つまるところ③では、
spl_autoload_register()
を使い、loadClassLoader()
をオートロード関数として登録。\Composer\Autoload\ClassLoader
クラスがloadClassLoader()
により読み込まれ、vendor
ディレクトリへの絶対パスをパラメータとして\Composer\Autoload\ClassLoader
クラスのインスタンスを作成、self::$loader
と$loader
に代入。spl_autoload_unregister()
を使い、loadClassLoader()
をオートロード関数から削除という処理が行われていることが分かりました。
④の処理:マッピング情報の読み込み方法を決定
vendor/composer/autoload_real.php$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());④では論理演算式によって、どのような読み込み方法を取るのかを決定しています。
この値によって、この後の⑤が⑤´に分岐するが決まります。
ここも②と同様本筋からそれますので軽い説明程度にとどめておきます。
条件式 説明 PHP_VERSION_ID >= 50600
PHPのバージョンが5.6.0以上であれば true
!defined('HHVM_VERSION')
HHVM
というvirtual machine
を使用していなければtrue
(!function_exists('zend_loader_file_encoded')
zend guard loader
というランタイムを使っていなければtrue
!zend_loader_file_encoded()
zend guard loader
を使っていてもzend guard
によりエンコードされていなければtrue
(
HHVM_VERSION
とzend_loader_file_encoded
についてはかなりあいまいな説明です。有識者の方がいらっしゃっいましたらコメントしていただけると助かります)正直ほとんどの方が、
PHP_VERSION_ID >= 50600
くらいにしか結果を左右されないと思います。ですので、PHPバージョンが5.6.0以上であれば
$useStaticLoader
にはtrue
が入ると思って差し支えないでしょう。(差し支えありましたらすいません)⑤の処理:マッピング情報を読み込み
ここから先、
$useStaticLoader
の値によって⑤と⑤´に処理が分かれますが、行っていることとしてはどちらとも、クラスとソースコードのマッピング情報を$loader
にセットしている処理になりますので、そのことを念頭に置いておくと良いかもしれません。
$useStaticLoader
がtrue
の場合はこちらになります。vendor/composer/autoload_real.phprequire __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::getInitializer($loader));ここではまず、
vendor/composer/autoload_static.php
をrequire
しています。その後に、
\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e
というまた長ったらしい名前をしているクラスのクラスメソッド、getInitializer()
を実行しているようです。最後に
getInitializer()
から返されるクロージャ(でしょうか?)をcall_user_func()
を使って実行しています。おそらく
\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e
クラスの定義もvendor/composer/autoload_static.php
で行われているでしょうから、そちらを見てみましょう。このファイルを開くと、人によっては数千行にも及ぶソースコードが表示されると思います。
ですが記述のほとんどが、[クラス名 => 定義されているソースコードへのパス]という連想配列で構成されているはずです。
なので
\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e
の実態は、以下のようにpublic
なクラスプロパティが5つとpublic
なクラスメソッドが1つだけのクラスです。<?php namespace Composer\Autoload; class ComposerStaticInitcde23787628405c61112bd11321f024e { public static $files = array ( // ... ); public static $prefixLengthsPsr4 = array ( // ... ); public static $prefixDirsPsr4 = array ( // ... ); public static $prefixesPsr0 = array ( // ... ); public static $classMap = array ( // ... ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixesPsr0; $loader->classMap = ComposerStaticInitcde23787628405c61112bd11321f024e::$classMap; }, null, ClassLoader::class); } }今回
vendor/composer/autoload_real.php
で呼び出されたのは::getInitializer()
ですので、そちらを詳しく見てみましょう。以下、
getInitializer()
の抜粋です。public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixesPsr0; $loader->classMap = ComposerStaticInitcde23787628405c61112bd11321f024e::$classMap; }, null, ClassLoader::class); }やはり、
\Closure::bind()
によってクロージャが返されていました。ところで、この
\Closure::bind()
は一体何をしているのでしょうか。(ここでは
\Closure::bind()
について簡単な説明のみにとどめておきます。
\Closure::bind()
の詳しい説明は公式マニュアルに記載されていますのでご参照ください)\Closure::bind()について
かいつまんで説明すると、
\Closure::bind()
は第1引数に指定した関数に対して、第2引数に指定したオブジェクトと第3引数に指定したクラスのスコープを関連付けてクロージャを複製する
メソッドです。
もう少しわかりやすい例でいうと
第3引数にクラス名を指定することで、複製されたクロージャはまるでそのクラスに実装されたメソッドであるかのように、そのクラスに定義されているプロパティに対して(
private
であっても)アクセスが可能となりますまた今回の例では関係ないのですが、
第2引数にインスタンスを渡すことで、クロージャ内で
$this
と記述した部分にそのインスタンスが割り当てられます
\Closure::bind()
について具体的な説明以下のような
Foo
クラスがあります。class Foo { private static $sFoo = "private static foo\n"; private $foo = "private foo\n"; }このクラスはプロパティを2つ持っていますが両方とも
private
なプロパティなのでecho Foo::$sFoo; // PHP Fatal error: Uncaught Error: Cannot access private property Foo::$sFoo in ... echo (new Foo())->foo; // PHP Fatal error: Uncaught Error: Cannot access private property Foo::$foo in ...のように外部からアクセスしようとしても当然エラーが起きます。
そこで、
\Closure::bind()
を使いprivate
なプロパティへアクセスできるクロージャを作成してみましょう。class Foo { private static $sFoo = 'private static foo\n'; private $foo = 'private foo\n'; } $cls = \Closure::bind(function (): void { echo Foo::$sFoo; (new Foo())->foo; }, null, Foo::class); $cls();すると、
private static foo private fooと
private
なプロパティを出力することができました。このクロージャはさながら、
class Foo { private static $sFoo = 'private static foo\n'; private $foo = 'private foo\n'; public function cls() { echo Foo::$sFoo; (new Foo())->foo; } }のようなクラスを定義した時の、
cls()
メソッドと同等の振る舞いをしているものだとみなせます。ただし違いが1つあり、それはメソッドなら再利用可能だが、
\Closure::bind()
によって複製されたクロージャは他の変数に保存しないと再利用不可になるという点です。これは、初期化処理のように最初の1回だけしかその処理が必要じゃない、しかし
private
やprotected
されたプロパティに対して処理を施したい、という場面でこそ有効な特徴になると思います。では本筋に戻ります。
以上の説明を踏まえて、
\Closure::bind()
が何をやっているのかを見てみましょう。以下、
::getInitializer()
の再掲です。public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixesPsr0; $loader->classMap = ComposerStaticInitcde23787628405c61112bd11321f024e::$classMap; }, null, ClassLoader::class); }
\Closure::bind()
の第1引数に複製したい無名関数を渡すことは先ほど説明しました。第2引数は
null
、つまりどのインスタンスともバインドしない静的メソッドとして複製することを示しています。第3引数では、
class
キーワードを使ってClassLoader
クラスの完全修飾名を渡しています。つまりここで複製されるクロージャは、まるで
\Composer\Autoload\ClassLoader
クラスに実装されたメソッドであるかのように\Composer\Autoload\ClassLoader
クラスのプロパティに対してアクセスが可能となっているクロージャです。そのことを確認する方法として、試しに第3引数を
ClassLoader::class
からnull
に変更した後php vendor/autoload.phpをコンソール上で実行してみてください。
PHP Fatal error: Uncaught Error: Cannot access private property Composer\Autoload\ClassLoader::$prefixLengthsPsr4というようなエラーが出力されるはずです。どうやら、
$loader->prefixLengthsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixLengthsPsr4;の部分で
Composer\Autoload\ClassLoader
のprivate
なプロパティにアクセスしてしまっているようですね。
Composer\Autoload\ClassLoader
クラスのプロパティを確認してみましょう。<?php namespace Composer\Autoload; class ClassLoader { private $vendorDir; // PSR-4 private $prefixLengthsPsr4 = array(); private $prefixDirsPsr4 = array(); private $fallbackDirsPsr4 = array(); // PSR-0 private $prefixesPsr0 = array(); private $fallbackDirsPsr0 = array(); private $useIncludePath = false; private $classMap = array(); private $classMapAuthoritative = false; private $missingClasses = array(); private $apcuPrefix; private static $registeredLoaders = array(); public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; } // 後略 }このようにすべて
private
で定義されています。ですので先ほどのクロージャからではクラス外からのアクセスになってしまうので、
Cannot access private property
と怒られてしまったんですね。以上で、
\Closure::bind()
自体の説明は終わりです。
vendor/composer/autoload_static.php
の変更した部分をClassLoader::class
に戻して、php vendor/autoload.phpがエラーを出力することなく完了したことを確認したら次に進みましょう。
\Closure:bind()
に渡した無名関数では、
\Closure:bind()
が複製する対象の無名関数を見てみましょう。以下、無名関数の部分のみの抜粋です。
function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInitcde23787628405c61112bd11321f024e::$prefixesPsr0; $loader->classMap = ComposerStaticInitcde23787628405c61112bd11321f024e::$classMap; }無名関数の中で外部の変数を使う場合は、予め
use
を使って内部で使う変数を渡してあげる必要があります。今回では、
$loader
が外部の変数ですのでuse
に含めておきます。先ほど示した、
\Composer\Autoload\ClassLoader
クラスのプロパティにこの4つのプロパティもあります。これらのプロパティに対してそれぞれ、
ComposerStaticInitcde23787628405c61112bd11321f024e
クラス(つまり自クラスですね)で定義されている同名のクラスプロパティを代入しています。つまりこの無名関数の中で、オートローディングに必要なマッピング情報を
$loader
に渡しているのです。以上でこの無名関数に関する説明は終了です。
まとめ
さて、⑤の処理の流れを総括すると
\Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e
が呼び出せるように、require
でvendor/composer/autoload_static.php
を読み込み
ComposerStaticInitcde23787628405c61112bd11321f024e::getInitializer($loader)
の戻り値をcall_user_func()
の引数に指定
ComposerStaticInitcde23787628405c61112bd11321f024e::getInitializer()
では、渡ってきた$loader
を無名関数の処理に組み込み、適切なクラススコープを付与したクロージャを返す。
call_user_func()
は返ってきた無名関数を実行して、$loader
のプライベートなプロパティにマッピング情報を渡して読み込ませる。といったことが行われていることが分かりました。
⑤´の処理:⑤と異なる方法でマッピング情報を読み込み
こちらは
$useStaticLoader
がfalse
の場合の処理になります。vendor/composer/autoload_real.php$map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); }大きく分けて3つの処理に分けることができますが、どれも流れは同じです。
まずは
vendor/composer/autoload_namespaces.php
を見てみましょう。vendor/composer/autoload_namespaces.php<?php $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( 'Mockery' => array($vendorDir . '/mockery/mockery/library'), 'Highlight\\' => array($vendorDir . '/scrivo/highlight.php'), 'HighlightUtilities\\' => array($vendorDir . '/scrivo/highlight.php'), );このように、[名前空間 => 対応させるファイルパスの入った配列]という連想配列を
$map
に返しています。これを、
foreach
を使って$namespace
と$path
に切り分けながら1つずつ$loader->set()
にかけているようです。名前から想像できると思いますが一応、中身を見てみましょう。
vendor/composer/ClassLoader.phppublic function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } }このメソッドでは、
$prefix
つまり$map
のキーが空文字だったらfallbackDirsPsr0
プロパティにパスを追加
$prefix
があれば、prefixesPsr0
プロパティ内の$prefix[0]
つまり頭文字に対応する配列>prefixesPsr0[$prefix[0]]
に[$prefix => array($path)]
の形で追加している処理を行っていると思われます。
同様に
vendor/comoser/autoload_psr4.php
を見てみると、vendor/comoser/autoload_psr4.php<?php $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( 'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src'), // ... );と、
autoload_namespaces.php
と同様の構造でマッピング情報を$map
に返します。そしてやはり先ほどと同様、
foreach
を使って$namespace
と$path
に切り分けながら1つずつ$loader->setPsr4()
にかけているようです。こちらも実装を見てみましょう。
vendor/composer/ClassLoader.phppublic function setPsr4($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } }流れを追うと、
$prefix
つまり$map
が空文字だったらfallbackDirsPsr0
プロパティにパスを追加
$prefix
の最後の文字が'\\'
でなければ例外を投げる。そのどちらでもなければ
- 先ほどと同様の指定方法で
prefixLengthsPsr4[$prefix[0]]
に[$prefix => $length]
を保存prefixDirsPsr4
プロパティに[$prefix => array($paths)]
の形で保存という処理が行われているようです。
これまた同様に、
vendor/composer/autoload_classMap.php
を見てみましょう。vendor/composer/autoload_classMap.php<?php $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( 'App\\Console\\Kernel' => $baseDir . '/app/Console/Kernel.php', 'App\\Exceptions\\Handler' => $baseDir . '/app/Exceptions/Handler.php', // ... );先ほど2つと同様に連想配列の形式です。ただし、値には配列ではなくて文字列を指定しているようです。
このマップ情報を
$classMap
に返し、$classMap
が空ではなければ$loader->addClassMap()
に渡しているようです。以下、
addClassMap()
の実装です。public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } }これは見たまんまの処理ですね。
既に
classMap
プロパティに何かあれば、渡された$classMap
をclassMap
プロパティに統合
classMap
プロパティが空なら、$classMap
をそのままclassMap
プロパティに代入となります。
以上3つの読み込み処理を経て、⑤´でもマッピング情報の読み込みが行われています。
⑥の処理:オートローダーの登録
⑥の処理を見てみましょう。
vendor/composer/autoload_real.php$loader->register(true);おそらく、メソッド名から鑑みてオートロードを行う上で重要な処理を担っているものだと思われます。
それでは
register()
の定義を見てみましょう。
vendor/composer/ClassLoader.php
を探してみますと確かにClassLoader
クラスのメソッドとして定義されていました。vendor/composer/ClassLoader.phppublic function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); if (null === $this->vendorDir) { //no-op } elseif ($prepend) { self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; } else { unset(self::$registeredLoaders[$this->vendorDir]); self::$registeredLoaders[$this->vendorDir] = $this; } }処理を追ってみます。
復習になりますが、
spl_autoload_register()
は未定義のクラス、インターフェースが呼び出されたときに実行されるオートロード関数を登録しておけるPHPの関数でしたね。ここでは、オートロード関数として自インスタンスの
loadClass
メソッドをオートロード関数として実行順を最優先で登録しています。登録したオートロード関数が何をするのかは後で詳しく見るとして、続けて
if
文の処理を見てみましょう。記述されている条件分岐は以下のようになります。
$this->vendorDir
がnull
、つまりインスタンス生成時にパスを渡さしていなかった場合何もしない
$this->vendorDir
が設定されていて、かつregister()
の引数$prepend
にtrue
を渡していた場合、[$this->vendorDir => $this]
という配列を順番的に最初にしてself::$registeredLoaders
に追加。
$prepend
がfalse
なら、self::$registeredLoaders
から$this->vendorDir
をキーとする要素を削除した後、順番的に一番後ろに[$this->vendorDir => $this]
という配列を追加。どうやら、
if
文ではself::$registeredLoaders
というクラスプロパティに、「この
vendorDir
のパスの時は、このローダを使う」という情報を[$this->vendorDir => $this]
という形式の配列で蓄積しているようです。
register()
メソッドの行っていることはこれで以上です。では先ほどオートロード関数として登録した
loadClass
メソッドの実装を見てみましょう。vendor/composer/ClassLoader.phppublic function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } }オートロード関数には実行時に、引数へ呼び出したパラメータが渡されることは既に説明致しました。
処理を予測してみると、
$this->findFile($class)
によってクラスが定義されているファイルの所在を探し出し、見つかればincludeFile($file)
でそれをinclude
する。といったところでしょうか。
実際に、
includeFile()
関数はvendor/composer/ClassLoader.php
の一番下で定義されています。vendor/composer/ClassLoader.phpfunction includeFile($file) { include $file; }純粋に渡されたファイルパスを
include
するだけの関数のようです。ですので
loadClass()
メソッドの肝はどうやら$this->findFile($class)
にありそうです。ではその実装を見てみましょう。
findFile():マッピング情報の検索
vendor/composer/ClassLoader.phppublic function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { // ① return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { // ② return false; } if (null !== $this->apcuPrefix) { // ③ $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // ④ // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { // ⑤ $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { // ⑥ apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // ⑦ // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; }少々長いです。前提として、
$class
には先ほどのloadClass
メソッドと同じものが渡されています。では割り振った番号ごとに処理を追ってみます。
①
vendor/composer/ClassLoader.phpif (isset($this->classMap[$class])) { // ① return $this->classMap[$class]; }
$this->classMap
に$class
のキーが見つかればその値を返します。②
vendor/composer/ClassLoader.phpif ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { // ② return false; }
$this->classMapAuthoritative
がtrue
もしくは$this->missingClasses
に$class
のキーが見つかれば、見つからなかったという意味のfalse
を返す。(
$this->classMapAuthoritative
はprefix
やfallback
のディレクトリ検索を禁止するかのプロパティでしょうか、デフォルトではfalse
なので禁止しません)③
vendor/composer/ClassLoader.phpif (null !== $this->apcuPrefix) { // ③ $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } }
$this->apcuPrefix
がnull
でなければ、APCuというPHPの拡張機能(?)が提供するapcu_fetch()
という関数を使ってキャッシュに$this->apcuPrefix.$class
というキーがないか検索、ヒットしたらその検索結果をリターンします。デフォルトでは
null
なのでここの部分はスキップされます。(この部分に関してはかなりあいまいな説明となっています、見識のある方がおられましたらご指摘していただけると助かります)
④
vendor/composer/ClassLoader.php$file = $this->findFileWithExtension($class, '.php'); // ④
$this->findFileWithExtension()
というメソッドを使ってファイルを探しているようです。かいつまんで説明すると、
findFileWithExtensiton()
メソッドは$this->prefixDirsPsr4
、$this->prefixesPsr0
から、$class
に対応するファイルパスを検索し、見つかればそれを返すメソッドになります。このメソッドの処理はコード量が長いので折りたたんで解説します。
findFileWithExtension()の解説
このメソッドでは、渡された文字列を地道に解析して
$this->prefixDirsPsr4
、$this->prefixesPsr0
からファイルパスを取り出しています。1つずつ解説するととても量が多くなるのでコメントに付記する形で説明を行います。
vendor/composer/ClassLoader.phpprivate function findFileWithExtension($class, $ext) { // 以下$class = Illuminate\\Support\\Facades\\Auth として実行 $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; // $logicalPathPsr4 = Illuminate/Support/Facades/Auth.php $first = $class[0]; // $first = I if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; // $subPath = Illuminate\\Support\\Facades\\Auth // $subPathから"\\"のうち最後のものの位置を$lastPosに代入。無ければfalseを返す while (false !== $lastPos = strrpos($subPath, '\\')) { /* $subPathがIlluminate\\Support\\Facades\\Authなら Illuminate\\Support\\Facadesに置き換え */ $subPath = substr($subPath, 0, $lastPos); $search = $subPath . '\\'; /* $searchが$this->prefixDirsPsr4にあればif文以下を実行 この場合であれば$subPath = Illuminate, $search = Illuminate\\, $lastPos = 10 の時にtrueとなる */ if (isset($this->prefixDirsPsr4[$search])) { /* substr()の結果がSupport/Facades/Authなので $pathEnd = /Support/Facades/Auth.php */ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); /* $searchに対応するファイルパスを$dirに取得。 この場合であれば $dir = /path/to/vendor/composer/../laravel/framework/src/Illuminate */ foreach ($this->prefixDirsPsr4[$search] as $dir) { // $file = /path/to/vendor/composer/../laravel/framework/src/Illuminate/Support/Facades/Auth.php if (file_exists($file = $dir . $pathEnd)) { // $fileの位置にファイルがあればtrue return $file; // $fileのパスを返す } } } } } // $this->fallbackDirsPsr4があればループを実行 foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // 以下、$class = Mockery, $logicalPathPsr4 = Mockery.php, $first = M として実行 // $classに"\\"があれば$posに最後のそれの位置を代入。無ければfalseを返す if (false !== $pos = strrpos($class, '\\')) { // ここでは$class = Hightlight\\Hogehoge, $$logicalPathPsr4 = Hightlight/Hogehoge.php として実行 // $logicalPathPsr0 = Hightlight/Hogehoge.php $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // $logicalPathPsr0 = Mockery.php $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { // $this->prefixesPsr0["M"] があればif文以下を実行 // $this->prefixesPsr0["M"]を$prefixと$dirsに切り分けてループ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { // ここでは$prefix = Mockery として実行 // $classに"Mockery"があり、その最初の位置が0番目ならif文以下を実行 if (0 === strpos($class, $prefix)) { // $dirsを1つずつループ foreach ($dirs as $dir) { // ここでは$dir = /path/to/vendor/composer/../mockery/mockery/library として実行 /* $file = /path/to/vendor/composer/../mockery/mockery/library/Mockery.php $fileがあれば$fileを返す */ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // $this->fallbackDirsPsr0があればループを実行 foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // $this->useIncludePathがtrueで、$logicalPathPsr0のファイルパスが存在すれば$fileを返す if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } // ここまで来たら何も見つからなかったということでfalseを返す return false; }⑤
vendor/composer/ClassLoader.php// Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { // ⑤ $file = $this->findFileWithExtension($class, '.hh'); }コメントアウトされた文章にもあるように
HHVM
が起動している場合に限り、$file
がfindFileWithExtension()
によって見つからなかったときに、拡張子を.hh
に変えて再びfindFileWithExtension()
を実行し、ファイルパスを取得しようと試みています。⑥
この時点でファイルパスの検索結果は
$file
に保存されています。
ですので後の処理は次回以降の処理を省くためのものがほとんどです。vendor/composer/ClassLoader.phpif (null !== $this->apcuPrefix) { // ⑥ apcu_add($this->apcuPrefix.$class, $file); }
$this->apcuPrefix
がnull
でなければ、APCuというPHPの拡張機能(?)が提供するapcu_add()
という関数を使ってキャッシュに、$this->apcuPrefix.$class
というキーで検索結果をキャッシュに追加しています。デフォルトでは
null
なのでここの部分はスキップされます。(この部分に関してもかなりあいまいな説明となっています、見識のある方がおられましたらご指摘していただけると助かります)
⑦
vendor/composer/ClassLoader.phpif (false === $file) { // ⑦ // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file;検索の結果何も見つからなかった場合、次回以降の検索処理を省略するために
$this->missinglasses
に[$class => true]
という形式の配列を追加します。これによって、次回以降同じクラス名を呼び出した際に①から⑥まで処理を経なくとも②の時点で該当ファイルがないという結果を返すことができます。
以上の処理を経て
findFile()
メソッドは呼び出しクラスに対するファイルパス情報を検索しているのですね。⑦の処理:クラスファイル以外のソースコードを読み込む
いよいよ最後の処理です。ここでは、クラスファイル以外のもの、例えば起動処理をまとめただけのファイルでしたり、ヘルパ関数をまとめたファイルなどをハッシュ値と紐づけて読み込ませる処理を行っています。
vendor/composer/autoload_real.phpif ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequirecde23787628405c61112bd11321f024e($fileIdentifier, $file); } return $loader;では処理を追ってみましょう。
まず、
$useStaticLoader
の値によって処理が分岐していますがどちらとも$includeFiles
に[ハッシュ値 => ファイルパス]
の構造をした配列を代入しています。
Composer\Autoload\ComposerStaticInitcde23787628405c61112bd11321f024e::$files
プロパティにも、vendor/composer/autoload_files.php
の戻り値にも同様の配列が記載されていると思います。この配列の要素をハッシュ値
$fileIdentifier
とファイルパス$file
に切り分けて1つずつ、composerRequirecde23787628405c61112bd11321f024e()
という関数にかけているようです。この
composerRequirecde23787628405c61112bd11321f024e()
関数はvendor/composer/autoload_real.php
の一番下、クラスの外に定義されています。実装を見てみましょう。
vendor/composer/autoload_real.phpfunction composerRequirecde23787628405c61112bd11321f024e($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } }この関数によって、クラスファイル以外のソースコードが読み込まれているようです。
処理を追うと、
$GLOBALS['__composer_autoload_files'][$fileIdentifier]
が空でないか確認空であればそのハッシュ値に対応するファイルがまだ読み込まれていないので
require
する
$fileIdentifier
に対応するファイルを読み込んだことを記憶しておくために、
$GLOBALS['__composer_autoload_files'][$fileIdentifier]
にtrue
を代入。といった流れになっています。
この処理が終わると、
getLoader()
は$loader
をリターンして処理を終了します。getLoader()の処理まとめ
とても長くなってしまいましたが以上で
getLoader()
の説明は終わりになります。最後に各番号の処理を簡単に説明します。
self::loader
がすでにあればそれを返す。実行環境を確認。
コンストラクタの引数に
/path/to/vendor
を指定して\Composer\Autoload\ClassLoader
クラスをインスタンス化、それをself::$loader
と$loader
に代入。
useStaticLoader
をするか決定。マッピング情報を
$loader
に読み込ませる。実際にオートロード処理を登録。これ以降の未定義クラスの呼び出しはマッピング情報をもとに解決される。
クラスファイル以外のソースコードをハッシュ値と対応させて読み込む。その後
$loader
をリターンするこのような処理の流れで、オートロード機能の構築が行われているということがわかりましたね。
この
::getLoader()
によって生成された$loader
は呼び出し元であるvendor/autoload.php
に返ってきます。return ComposerAutoloaderInitcde23787628405c61112bd11321f024e::getLoader();返ってきた
$loader
をvendor/autoload.php
は更にリターンしています。なのでもし外部でローダーを使う処理を実装したい場合は$loader = require "/path/to/vendor/autoload.php";とすれば
::getLoader()
によって生成されたローダーを使うことができます。今回起点としたLaravelの
index.php
ではpublic/index.php<?php // ... require __DIR__.'/../vendor/autoload.php'; // ...のようにローダーの保存は行わず、
::getLoader()
を介してオートロード関数の登録だけを行っています。保存を行わなかった場合でもクラスプロパティとしてローダーは保存されているので、未定義のクラスが呼び出された場合でもそちらに保存されているローダーの
loadClass
メソッドを通してオートロード処理が行われている、といったところでしょうか。処理の流れまとめ
では、最後に今まで説明してきたことを順序だてて簡潔に追ってみましょう。
public/index.php
でvendor/autoload.php
がrequire
される。
vendor/autoload.php
でvendor/composer/autoload_real.php
がrequire
され、そこに定義されているComposerAutoloaderInitcde23787628405c61112bd11321f024e::getLoader()
を実行。
::getLoader()
の処理の中でオートロード機能の実体となる\Composer\Autoload\ClassLoader
インスタンスを生成し、自クラスの静的プロパティとして保存。そのインスタンスに対してマッピング情報の読み込み、オートロード処理の登録、そしてクラス以外のソースコードに関しても読み込み、といった処理を行う。
このような流れでComposerによるファイルのオートロード処理が行われているのだということが分かりました。
終わりに
非常に長い説明になってしまったことをお詫びしたいと思います。
ですが、このように処理を1つ1つ丁寧に追っていったことでComposerのオートロード機能についてはほとんどぬけのない理解が出来たのではないでしょうか。
以前私のほうでも投稿しました記事でも述べたことがあるのですが、普段何気なく使っている機能について一度は深くまで追ってみるという経験をするのは、非常に力がつきます。
現に、この機能を調べるまで知りもしなかった情報や仕様について理解する機会を得られました。
月並みな締めになってしまいますが、皆さんも一度はこのように外部パッケージが提供している仕組みについて、1から深堀りして追ってみる経験をしてみてはいかがでしょうか。
- 投稿日:2021-03-02T11:11:04+09:00
SQLiteを使ったテストで初学者がはまったエラー ~Laravel~
前提
テスト用データベースはSQLiteインメモリを使用しています。
環境
PHP Laravel PHPUnit SQLite 7.4.5 7.28.4 8.5 3.28.0 エラー内容 「リレーションが辿れない」
前提
SQLiteは以下のデータ型の値しか格納しない。
型 内容 INTEGER 符号付整数。1, 2, 3, 4, 6, or 8 バイトで格納 NUMERIC 負号付きの整数 (1、2、3、4、6、8バイト) REAL 浮動小数点数。8バイトで格納 TEXT テキスト。UTF-8, UTF-16BE or UTF-16-LEのいずれかで格納 NONE カラムにデータを指定しなかった場合この型になる 以前Laravelで書いていたマイグレーションファイルの一部はこんな感じ。
profilesテーブル
/** * Run the migrations. * * @return void */ public function up() { Schema::create('profiles', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('user_id'); $table->string('name'); $table->text('introduction'); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users'); }); }profilesテーブルのuser_idカラムとusersテーブルidカラムで紐付かせている。
エラー
テストする際にこのリレーションの箇所でサーバーエラーが連発した。ここの紐づきだけでなく、他の全てのリレーションを辿るアクションでサーバーエラーが発生した。
ここでかなり詰まってしまった。原因が分からない。。。原因
いろいろ調べた結果、SQLiteがサポートしている型にエラーの原因が存在することが判明した。
SQLiteのサポートしている型一覧
サポートしている型 実際にSQLiteが格納する型 INT
INTEGER
TINYINT
SMALLINT
MEDIUMINT
BIGINT
UNSIGNED BIG INT
INT2
INT8INTENGER CHARACTER(20)
VARCHAR(255)
VARYING CHARACTER(255)
NCHAR(55)
NATIVE
CHARACTER(70)
NVARCHAR(100)
TEXT
CLOBTEXT NUMERIC
DECIMAL(10,5)
BOOLEAN
DATE
DATETIMENUMERIC REAL
DOUBLE
DOUBLE PRECISION
FLOATREAL BLOB NONE どういうことかというと、テーブル作成の際にカラムの型を指定をするが、そのカラムの型がSQLiteでは使用できない型である場合、SQLite自身がSQliteでも扱えるデータ型に強制的に変換し、データを格納する。
表の左側が変換対象のカラムの型、表の右側は変換後の型。これを踏まえて上のマイグレーションファイルをもう一度確認してみると、外部キーに設定しているuser_idカラムの型は「unsignedInttenger」。
SQLiteを見てみると、「UNSIGED BUG INT」は存在するが、「UNSIGNED INT」は存在しない。つまり外部キーに設定したカラムの型は、SQLiteではサポートされていなかったということ。
したがってprofilesテーブルのuser_idカラムの値はINTENGERと扱われず、その結果リレーションメソッドを用いても紐づきを辿ることができず、サーバーエラーになったっぽい。解決案
外部キーカラムを「UNSIGNED INT」型にしてしまえばよい。
僕はLaravel7で新しく導入された外部キーのフォーマットにしたがってマイグレーションファイルを書き換えた。
profilesテーブル(変更後)
/** * Run the migrations. */ public function up(): void { Schema::create('profiles', function (Blueprint $table) :void { $table->id(); $table->foreignId('user_id')->constrained(); $table->string('name'); $table->text('introduction'); $table->timestamps(); }); }変更前の外部キー設定
$table->unsignedInteger('user_id');$table->foreign('user_id')->references('id')->on('users');変更後の外部キー設定
$table->foreignId('user_id')->constrained();この変更された外部キー設定の特徴は、
・1行で外部キー設定が完了する(テーブル名とカラム名が規約通りではない場合、constrainedの引数にテーブル名を指定する)
・この外部キーのカラムの型は自動的に「unsignedBigInteger」となるしたがって、このフォーマットにしたがって外部キー設定をすることで外部キーカラムの型は「UNSIGNED BIG INT」型になり、SQLiteのサポート範囲内となり、きちんとINTENGER型としてデータを格納してくれる。
結果、リレーションを辿ることができた。
補足
ミドルウェアにて以下のポリシーを用いてアクセス制限をしていた。/** * プロフィール編集の権限があるかチェック * * @param User $user * @return mixed */ public function view(User $user) { $profile = $user->profiles()->first(); return $user->id === $profile->user_id; }SQLiteを使用したテストのときだけこのポリシーを通過できなかった。
「===」を使って厳密な型比較せず、「==」に変更した場合はクリアした。
外部キーをunsignedBigIntengerにして、SQLiteにIntengerとして扱ってもらったが、厳密な型比較をすると、なぜか差異がでるそう。。。ここらへんはまだ理解していません。。。最後に
このように対処することで解決致しましたが、何か間違いがあるかもしれません。
何かお気づきの方いらっしゃいましたら、気軽にコメントのほどよろしくお願い致します。
- 投稿日:2021-03-02T00:35:37+09:00
初学者がPHPを学び始める4
フォームを作成する
フォームを作りたい時は、HTMLのformタグを用います。
action属性にはデータを渡す先のURLを指定します。
method属性は値の送信の方法で、「get」と「post」のどちらかを指定します。getの場合は送信される値がURLに表示され、postの場合はURLに表示されません。
以下の場合はフォームに入力した値をsent.phpというページに送信するという意味になります。<form action = "sent.php" method = "post"></form>テキストボックスを作成する
テキストボックスをつくるにはHTMLのinput type="text"を使います。name属性は入力された値を取得するときに使う名前です。後にフォームのデータを受け取るときに使用します。またinputタグは閉じタグが必要ありません。(textareaタグの場合は必要)
以下のように記述します。
inputは一行分のテキストが入力できます。
textareaの場合は複数行のテキストが入力できます。<input type = "text" name = "email"> <textarea name = "body"></textarea>送信ボタンを作る
送信ボタンをつくるにはinput type="submit"を用います。
value属性に指定された値がボタン上に表示されます。
以下の記述で送信ボタンを作ることができます。(cssは別に用意する必要があります。)<input type = "submit" value = "送信">フォームで送信した値を受け取るには「$_POST」を使用します。
「$_POST」は連想配列になっています。[ ]の中に、inputとtextareaのname属性に指定した値を入れることで、それぞれの送信した値を受け取ることが出来ます。
以下の記述で送られてきた値を(email)を受け取ることができます。<?php echo $_POST['email']; ?>セレクトボックスの作り方
セレクトボックスをつくるには図のようにselectタグの中にoptionタグを並べます。
optionタグの中身が選択肢として表示されます。
selectタグには「$_POST」で値を受け取るためのname属性を指定します。
optionタグのvalue属性が送信される値です。以下の記述でセレクトボックスが表示できます。
<select name = "age"> <option value ="未選択">選択してください</option> <option value ="20代">20代</option> <option value ="30代">30代</option> </select>以下の記述でageを受け取り出力することができます。
<?php echo $_POST["age"]; ?>forを使ったセレクトボックス
下記の場合だとセレクトボックスに1から100までの数字を選択することができる。
<?php for($i = 1; $i <= 100; $i++){ echo "<option value='{$i}'>{$i}</option>"; } ?> この二つは同じ意味↑↓ echo "<option value ="1">1</option>" echo "<option value ="2">2</option>" echo "<option value ="3">3</option>"