「PHP」の版間の差分

提供:senooken JP Wiki
(Logging)
(FETCH_MODE)
 
(同じ利用者による、間の106版が非表示)
112行目: 112行目:


「[https://packagist.org/search/?tags=search Packagist]」の検索結果をみても、tntsearchが特に人気の模様。
「[https://packagist.org/search/?tags=search Packagist]」の検索結果をみても、tntsearchが特に人気の模様。
== About==


== Laravel ==
=== About ===
人気の理由
PHPはプログラミング言語だ。汎用的なプログラミング言語だが、主にウェブサーバー上のソフトウェアで使用される。


「[https://freelance.techcareer.jp/articles/wp/skills/larabvel/detail/9090/ Laravelの人気を大検証 何が凄いの? | テクフリ]」
GNU socialはPHPで記述されている。他にもWordPress・NextCloudなどがPHPで記述されている。これらのPHP製ソフトウェアはVPSだけではなく安価なレンタルサーバーでも動作するため、低コストで運用することができる。


CakePHPのほうが早い。
PHPの公式リファレンスは日本語版があり、わかりやすくまとまっている。
*[https://www.php.net/manual/ja/langref.php PHP: 言語リファレンス - Manual]
ウェブ上にはPHPに関するTipsが多く公開されており、大抵の疑問はウェブ検索で解決できる。


* 簡単にマスター可能
=== Version ===
* 自由度が高い
PHPは言語の版数が上がる際、過去の版と互換性の無い破壊的変更がなされることがある。


=== Getting Started ===
開発者はこのリスクを軽減するために、非推奨の言語機能を避け、実行時の警告 (warning) を適切に処理するべきだ。


==== Configuration ====
GNU socialは現在PHP 7系で動作する様に記述されており、PHP 8系への対応は作業途中だ。
[https://laravel.com/docs/5.8/configuration Configuration - Laravel 5.8 - The PHP Framework For Web Artisans]


===== Environment Configuration =====
==== PHP v8 ====
DotEnvライブラリーを使っており、ルートディレクトリーの.env.exampleを.envにリネームするとこの設定を利用する。
PHP v8になっていろいろ更新が入った。特にPHP v7.4からv8に更新する際のポイントがあるので整理する ([https://web.gnusocial.jp/post/2023/12/08/9535/ 行事: 「12月にPHP8.3が出るので、PHP8で増えた文法をおさらいしましょうセミナー」参加報告 | PHP8対応の肝は型とエラーレベル | GNU social JP Web])。


==== Directory Structure ====
大きく以下2点がある。
[https://laravel.com/docs/5.8/structure Directory Structure - Laravel 5.8 - The PHP Framework For Web Artisans]
#エラーレベルの上昇。
* app
#型の厳格化。
* bootstrap
エラーレベルが1段階上がったため、今までWarningで問題なかったものがFatal Errorになって動作しなくなる。他に、型が厳格になっている。
* config
* database
* public
* resources
* routes
* storage: フレームワークで自動生成される、コンパイル済みのBladeテンプレート、ファイル系セッション、ファイルキャッシュ類を格納。app, framework, logsがある。
** app: アプリ生成ファイルの格納。
** framework: フレームワーク生成ファイルとキャッシュ。
** logs: ログ。
* tests
* vendor
Laravelの名前空間と、ディレクトリーは同一ではない。


* [https://terakoya.sejuku.net/question/detail/30845 Appは大文字?小文字? | プログラミング学習サイト【侍テラコヤ】]
具体的には、php.ini/.user.iniで以下を指定して、PHP v7.4時点で警告にできるだけ対応しておく。
* [https://teratail.com/questions/273090 laraverlのnamespaceでAppが大文字で始まる理由]
error_reporting=E_ALL ; -1
続いて、phpソースファイルに以下を記入して型を厳密にしておく。
declare(strict_types=1);
チェックツールがあるのでこれを使うと問題箇所などがわかる。
*PHP CodeSniffer
*PHPStan
*Rector
まず上記2個を試して、おまけでRectorも試すとよい。


命名規則の都合だろうと思われる。少々気持ち悪い。
=== Guide ===
Ref:
*[https://notabug.org/gnusocialjp/gnusocial/src/main/DOCUMENTATION/DEVELOPERS/CONTRIBUTING/coding_standards.md DOCUMENTATION/DEVELOPERS/CONTRIBUTING/coding_standards.md]
*[https://www.php-fig.org/psr/psr-12/ PSR-12: Extended Coding Style - PHP-FIG]
PHPのコーディングの推奨規約がある。PSR-12というのがメジャーな模様。GNU socialでも採用されている。
 
「[https://stackoverflow.com/questions/1209720/what-should-i-name-my-php-class-file What should I name my PHP class file? - Stack Overflow]」にあるように、PSRではファイル名には記載がない。PSR-4や「[https://www.php-fig.org/bylaws/psr-naming-conventions/ PSR Naming Conventions - PHP-FIG]」に記載がある程度。
 
ただ、「[https://framework.zend.com/manual/1.12/en/coding-standard.naming-conventions.html Manual - Documentation - Zend Framework]」、「[https://book.cakephp.org/4/en/intro/conventions.html#file-and-class-name-conventions CakePHP Conventions - 4.x]」など、他の規約があり、クラス名と同じになっている。
 
PHPのクラス名は大文字小文字を区別しないが、わかりにくいので大文字小文字で、クラス名と一致させておくとよさそう。


=== Architecture Concepts ===
ただし、viewなど、表示に直接結びついているものは、小文字でもいいかも。ファイル名とURLパスが同じほうが分かりやすい。


==== Service Container ====
=== Tool ===
[https://laravel.com/docs/5.8/container Service Container - Laravel 5.8 - The PHP Framework For Web Artisans]


==== Service Class ====
* [https://creatopia.jp/media/12102 PHPをブラウザで実行、動作確認できるおすすめツール4つ|Creatopia Media]


* [https://laravel.com/docs/5.8/container Service Container - Laravel 5.8 - The PHP Framework For Web Artisans]
PHPをWebブラウザーで実行、動作確認のツールがいくつかある。
* [https://medium.com/@laravelprotips/understanding-laravel-service-classes-a-comprehensive-guide-1f22310c70bd Understanding Laravel Service Classes: A Comprehensive Guide | by Laravel Pro Tips | Medium]


Serviceクラスは特定のビジネスロジックの管理に重点を置いたクラス。ビジネスロジックに特化しているから、他のクラスと異なり、通常はプロパティーを継承しない。
* paiza.IO: [https://paiza.io/ja/projects/new Online PHP Editor | ブラウザでプログラミング・実行ができる「オンライン実行環境」| paiza.IO]: パーマリンク。
* phpsansbox.io [https://phpsandbox.io/ Write PHP online from your browser - PHPSandbox]: フレームワークも確認可能。
* OnlinePHP.io: [https://onlinephp.io/ PHP Sandbox - Execute PHP code online through your browser]: 複数バージョンの同時実行できる。
* 3v4l.org: [https://3v4l.org/ Online PHP editor | Test code in 250+ PHP versions]: パーマリンク。


app/Servicesに配置して、クラス名の末尾にはServiceの接尾辞をつける。
3v4l.orgがパーマリンクがあって、複数バージョンの動作確認できるので、これがいいと思う。
==Package manager==
===Composer===
PHPのパッケージ管理システム。PHP v5.3.2以上で動作する。
====Install====
Ref: [https://senooken.jp/post/2020/01/16/ インストール: Composer | モダンなPHPのパッケージマネージャー – senooken JP].
[ -e installer ] || wget <nowiki>https://getcomposer.org/installer</nowiki>
php installer --install-dir="$LOCAL/stow/$PKG-$VER/bin" --filename=$PKG
公式サイトからinstallerをダウンロードしてそれを使って任意の場所に設置する。
====Usage====
Composerを使う場合,<code>composer.json</code>ファイルを用意する。このファイルはプロジェクトの依存関係を記載する。VCSで管理すべきファイルだ。


多くの場合、Eloquentモデルにリンクされたロジックの追加で役立つ。例えば、Userモデルに対するUserServiceのような。ただ、Eloquentモデルとは関係なしに、PaymentServiceのように特定の機能 (ビジネスロジック) に合わせて調整したサービスクラスもある。
このファイルに使用するライブラリーを以下のように記入する。
<{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}
composer.jsonに指定する最初の項目はrequireキーだ。このキーで依存パッケージをComposerに知らせる。パッケージ名とバージョンを指定する。


ビジネスロジックに特化したユーティリティークラスとかに近い。
新規にパッケージを追加する場合は、以下のコマンドでインストールとcomposer.jsonへの追記を行えます。同時に、composer.lockファイルも作成されます。composer.lockも管理すべきファイル。
<code>composer require "monolog/monolog:1.0.*"</code>
パッケージ名はベンダー名とプロジェクト名から構成される。


うまい作りとしては、Controllerではtry-catchを含んだサービスクラスに定義した関数を呼ぶだけ、呼び終わった結果を返すだけにするとか。
1.0.*は1.0の任意のバージョンを示す。


コードがモジュール化されて、保守しやすく整理される。
composer.jsonを用意したら,以下のようにcomposerのinstallコマンドを実行する。
<code>php composer.phar install</code>
これにより,<code>vendor</code>ディレクトリーにパッケージがインストールされる。


=== The Basics ===
プロジェクトにgitを使っている場合,.gitignoreにvendorディレクトリーを追加したほうがいい。


==== Routing ====
Composerによるインストールが完了すると,composer.lockファイルにダウンロードしたパッケージとバージョンを出力する。composer.lockをプロジェクトリポジトリーに追加して,プロジェクトメンバー全員が同じバージョンのパッケージを使用する。


===== Basic Routing =====
composer.lockが存在するプロジェクトで上記コマンドを実行する場合,composer.jsonの内容に加えて,composer.lockの内容も参照されて,composer.lockと同じバージョンがインストールされる。
routes/web.phpでURL別のアクセス時 (ルーティング) の処理 を設定する。


web.phpはwebミドルウェアグループに割り当てられていて、CSRFガードなどが入っている。
パッケージを最新バージョンに更新したい場合,<code>composer update</code>コマンドを使う。このコマンドを実行すると,最新バージョンをインストールして,composer.lockも更新する。動作としては,composer.lockを削除後に<code>composer install</code>を実行することと等しい。composer updateは基本的には使わない。
Route::get('/user', 'UserController@index');
上記のような書式で、パスとアクションを対応付ける。


===== Named Routes =====
updateやinstallの後にパッケージ名を指定すると,指定したパッケージだけ更新やインストールできる。
ルートに名前を付けて、後で流用・参照できる。
<code>composer update monolog/monolog</code>
Composerでインストール可能な主なパッケージは,Packagistリポジトリーに配置されている。
====自動読み込み (Autoloading)====
ライブラリーの自動読み込みのために,Composerは<code>vendor/autoload.php</code>ファイルを生成する。以下のように,ライブラリーのクラスを使用するファイルの先頭でこのファイルを読み込めば使えるようになる。
<require __DIR__ . '/vendor/autoload.php';
$log = new Monolog\Logger('name');
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING));
$log->addWarning('Foo');
composer.jsonの<code>autoload</code>欄に指定することで,ライブラリー以外の自分のコードも自動読み込みの対象にできる。


==== Middleware ====
composer.jsonを編集した場合、<code>composer dump-autoload</code>を実行して<code>vendor/autolaod.php</code>を必ず更新します。
リクエスト受信後にコントローラー処理の前後に割り込んで行う処理の仕組み。プログラムの基本はコントローラーのアクション。


このアクションに共通処理を一括で仕込む場合、コントローラーのアクション単位で処理が必要になる。そのままだと何回も同じことを書く必要がある。
==== Libraries ====
[https://getcomposer.org/doc/02-libraries.md Libraries - Composer]


例えば、フォームの送信チェックやログイン認証など。これらを一括で行うための仕組みがミドルウェア。
自前のライブラリーをComposerでインストール可能な形式にする方法がある。


==== Controllers ====
===== Every project is a package =====
[https://laravel.com/docs/5.8/controllers Controllers - Laravel 5.8 - The PHP Framework For Web Artisans]
ディレクトリーにcomposer.jsonがあると、そのディレクトリーはパッケージになる。プロジェクトとパッケージの違いは、名前の有無。プロジェクトは名前のないパッケージという扱いになる。


===== Resource Controllers =====
パッケージをインストール可能にするにあたって、'''composer.jsonに最低限名前'''が必要。
{
    "name": "acme/hello-world",
    "require": {
        "monolog/monolog": "1.0.*"
    }
}
acme/hello-worldというプロジェクトになる。acmeはベンダー名で、ベンダー名は必須。
 
ベンダー名に迷う場合、GitHubのユーザー名が適している。'''パッケージ名は小文字必須'''。単語区切りは-にするのが慣例。
 
===== Library Versioning =====
VCSでパッケージを管理している場合、composerはVCSからバージョンを自動で判別する。VCSを使っていない場合だけ、versionプロパティーを追加する。
{
    "version": "1.0.0"
}
 
===== Publishing to a VCS =====
composer.jsonを用意したらVCSのリモートリポジトリーに公開する。ベンダー名とユーザー名は不一致でも問題ない。
 
公開したパッケージを取り込む場合、requireで指定する。
{
    "name": "acme/blog",
    "repositories": [
        {
            "type": "vcs",
            "url": "<nowiki>https://github.com/username/hello-world</nowiki>"
        }
    ],
    "require": {
        "acme/hello-world": "dev-master"
    }
}
パッケージ名hello-worldに必要なリポジトリーの情報をrepositoriesで指定している。たぶん、末尾のパッケージ名とリポジトリー名は一致が必要。
 
===== Publishing to packagist =====
VCSでの公開のケースは以上。ただ、repositoriesの情報は省略する方法がある。これは、Packagistに登録している場合。composerはpackagitstから同盟パッケージを探す。公開して問題ないなら、Packagistへの登録を検討する。
 
===== Light-weight distribution packages =====
.githubディレクトリーのように、パッケージに不要なファイルがある。
 
.gitattributesでパッケージやzipに含めないファイルを指定できる。
// .gitattributes
/demo export-ignore
phpunit.xml.dist export-ignore
/.github/ export-ignore
以下のコマンドで確認できる。
git archive branchName --format zip -o file.zip
パッケージに含まれないだけで、Gitリポジトリーには入っている。
 
==== Articles ====
 
===== Versions and constraints =====
[https://getcomposer.org/doc/articles/versions.md Versions and constraints - Composer]
 
composer requireなどで指定するパッケージのバージョンにはいくつか記法がある。このバージョン部分は、composerではversion constraint (バージョン制約) と呼んでいる。このバージョン制約で、チェックアウト対象を判断する。
~/my-library$ git branch
v1
v2
my-feature
another-feature
 
~/my-library$ git tag
v1.0
v1.0.1
v1.0.2
v1.1-BETA
v1.1-RC1
v1.1-RC2
v1.1
v1.1.1
v2.0-BETA
v2.0-RC1
v2.0
v2.0.1
v2.0.2
 
====== tag ======
基本的に、composerはタグを扱う。
 
上記のようなタグの場合、composerは先頭のvなどのプレフィクスを除外して考える。基本的にはこの中で、一番新しいものを優先的に探す。
 
====== branch ======
タグではなく、ブランチのチェックアウトが必要なら、特別なdev-*プレフィクス/サフィックスを指定してブランチを指定する。
 
上記の例で、my-featureブランチの指定が必要ならば、dev-my-featureを指定する。
 
ブランチ名がバージョン名と似ている場合、記法が変わる。v1.x-devのように指定する。v1タグではなく、v1ブランチを明示するために、.xは必須。あるいは、タグ名とブランチ名を完全に別の名前 (v1ブランチの代わりにv1.xブランチ) にしておけば、.xは不要。
 
バージョン名によく似たブランチ名を指定する場合だけ、dev-プレイフィクスではなく、-devサフィックスを指定する。
 
==phpDocumentor==
Ref: [https://phpdoc.org/ Home | phpDocumentor].
 
PHPのソースコードにコメントを残す際に、構文に従って記載すると、ツールで表示したり、文書に出力できたりする。ソースコードリーディングにも役立つので、積極的に記載したほうがよさそう。構文を整理しておく。
 
特に記法が大事。
*[https://docs.phpdoc.org/3.0/guide/getting-started/what-is-a-docblock.html phpDocumentor]
*[https://docs.phpdoc.org/3.0/guide/references/phpdoc/ phpDocumentor]
ファイル冒頭の<?php の直後あたりに書くと、ファイルレベルのDocBlockになる。逆にclassの直前などに書くと、ファイル冒頭でもclassレベルになる。
 
DocBlockは3部構成。
#Summary=短い説明。改行直前の.か空行で終わり。
#Description=長い説明。アルゴリズムの機能や、使用方法、例など。最初のタグか、改行、DocBlockの終端で終わる。
#Tags/Anntations=要素のメタ情報。新しい行の@から始まる。
具体例。<syntaxhighlight lang="php" line="1">
<?php
/**
* A summary informing the user what the associated element does.
*
* A *description*, that can span multiple lines, to go _in-depth_ into
* the details of this element and to provide some background information
* or textual references.
*
* @param string $myArgument With a *description* of this argument,
*                          these may also span multiple lines.
*
* @return void
*/
function myFunction($myArgument)
{
}
</syntaxhighlight>以下の要素に前置できる。
*require(_once)
*include(_once)
*class
*interface
*trait
*function (including methods)
*property
*constant
*variables, both local and global scope.
Inheritance
 
DocBlockはSummary/Descriptionを上書きしたり、拡張できる。@inheritdocを使う。
 
要素ごとに以下のタグを継承する。
{| class="wikitable"
{| class="wikitable"
!Verb
!Elements
!URI
!Inherited tags
!Action
!Route Name
|-
|-
|GET
|''Any''
|<code>/photos</code>
|[[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/author.html|@author]], [[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/version.html|@version]], [[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/copyright.html|@copyright]]
|index
|photos.index
|-
|-
|GET
|''Classes and Interfaces''
|<code>/photos/create</code>
|[[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/category.html|@category]], [[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/package.html|@package]], [[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/subpackage.html|@subpackage]]
|create
|photos.create
|-
|-
|POST
|''Methods''
|<code>/photos</code>
|[[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/param.html|@param]], [[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/return.html|@return]], [[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/throws.html|@throws]]
|store
|photos.store
|-
|-
|GET
|''Properties''
|<code>/photos/{photo}</code>
|[[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/var.html|@var]]
|show
|}@subpackageタグは同じ@packageの親クラスのときだけ継承される。
|photos.show
 
よく使う@param/@returnの構文。
<type-expression          = 1*(array-of-type-expression|array-of-type|type ["|"])
array-of-type-expression = "(" type-expression ")[]"
array-of-type            = type "[]"
type                    = class-name|keyword
class-name              = 1*CHAR
keyword                  = "string"|"integer"|"int"|"boolean"|"bool"|"float"
                            |"double"|"object"|"mixed"|"array"|"resource"|"scalar"
                            |"void"|"null"|"callable"|"false"|"true"|"self"
 
クラス名以外は全小文字。
 
基本は @<directive> <Type> <name> <description> の書式。スペース区切り。
*@property: クラスの注釈部で指定する。メンバー変数の説明。
==PHPUnit==
PHPUnitを使用すれば自動単体テスト (Unit test) が可能だ。
*[https://web.gnusocial.jp/post/2023/07/07/7428/ 設置: PHPUnit | PHPの定番テストフレームワーク  |  GNU social JP Web]
===Version===
情報源: [https://phpunit.de/supported-versions.html Supported Versions of PHPUnit – The PHP Testing Framework]。
 
PHPUnitのバージョンごとに対応しているPHPのバージョンが決まっている。
 
PHP v7.4に対応してい最後のバージョンはPHPUnit 9なので、当分はこれを使うのが良い。
===Basic===
出典: [https://docs.phpunit.de/en/9.6/writing-tests-for-phpunit.html 2. Writing Tests for PHPUnit — PHPUnit 9.6 Manual]。
 
基本的な使用方法を整理する。
#基本的にはクラス単位で試験コードを記載。<Class>クラスの試験コードは<Class>Testの命名にする。
#<Class>Test はPHPUnit\Framwork\TestCaseを継承させる。
#試験はpublicのtest*メソッドの命名にする。あるいは、@testのアノテーションを付ければ、命名規則に従わなくてもいい。
#test*メソッド内で、assertSame() などで、期待値との比較で試験を行う。
例:
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class StackTest extends TestCase
{
<nowiki> </nowiki>  private static $dbh;
<nowiki> </nowiki>  private $instance;
<nowiki> </nowiki> 
<nowiki> </nowiki>  public static function setUpBeforeClass(): void
<nowiki> </nowiki>  {
<nowiki> </nowiki>      // DB接続などクラス全体の初期化処理
<nowiki> </nowiki>      self::$dbh = new PDO(<nowiki>''</nowiki>);
<nowiki> </nowiki>  }
<nowiki> </nowiki>  public static function tearDownAfterClass(): void
<nowiki> </nowiki>  {
<nowiki> </nowiki>      self::$dbh = null;
<nowiki> </nowiki>  }
<nowiki> </nowiki>  protected function setUp(): void
<nowiki> </nowiki>  {
<nowiki> </nowiki>    // 該当インスタンスの生成などメソッド単位の初期化処理。
<nowiki> </nowiki>    $instance = new Stack();
<nowiki> </nowiki>  }
<nowiki> </nowiki>  public function testPushAndPop(): void
<nowiki> </nowiki>  {
<nowiki> </nowiki>      $stack = [];
<nowiki> </nowiki>      $this->assertSame(0, count($stack));
<nowiki> </nowiki>      array_push($stack, 'foo');
<nowiki> </nowiki>      $this->assertSame('foo', $stack[count($stack)-1]);
<nowiki> </nowiki>      $this->assertSame(1, count($stack));
<nowiki> </nowiki>      $this->assertSame('foo', array_pop($stack));
<nowiki> </nowiki>      $this->assertSame(0, count($stack));
<nowiki> </nowiki>  }
}
====Depends====
前回の試験で準備した結果を利用したい場合、@dependsのアノテーションでテスト関数を指定しておくと、指定したテスト関数のreturnを引数に受け継いだテスト関数を記述できる。
 
@dependsは複数指定でき、指定順に事前に試験が実行されて引数に渡される。
====Data Provider====
ある関数に対して、テストケースを用意して、複数の引数の組み合わせを試験したいことがよくある。こういうときのために、テスト関数に渡す引数を生成する関数のデータプロバイダーを指定できる。@dataProviderで関数を指定する。データプロバイダーは引数のリストを配列で返すようにする。
 
データ数が多い場合、名前付き配列にしておくと、どういうデータ項目で失敗したかがわかりやすい。
 
Iteratorオブジェクトを返してもいい。
====Fixtures====
出典: [https://docs.phpunit.de/en/9.6/fixtures.html 4. Fixtures — PHPUnit 9.6 Manual]。
 
テストメソッドの実行前に、テスト対象のインスタンスの生成や、DB接続など準備がいろいろある。これをFixturesと呼んでいる。この準備がけっこう手間になる。これを省力できるのがテストフレームワークの利点。
 
テストメソッド実行前後に共通で行える処理がある。
*setUp/tearDown: テストメソッド単位の前後処理。テスト対象インスタンスの生成など。tearDownは何もしなくてもいいことが多い。
*setUpBeforeClass/tearDownAfterClass: クラス単位の前後処理。 DB接続など。
====XML Configuration File====
出典:
*[https://docs.phpunit.de/en/9.6/organizing-tests.html 5. Organizing Tests — PHPUnit 9.6 Manual]
*[https://docs.phpunit.de/en/9.6/configuration.html 3. The XML Configuration File — PHPUnit 9.6 Manual]
基本はコマンドでテスト対象クラス・ファイルを指定してテストを実行する。他に、XMLの設定ファイル (phpunit.xml) でもテスト対象を指定できる。
 
testsディレクトリーの全*Test.phpを対象にする最小限の例は以下。
<phpunit bootstrap="src/autoload.php">
  <testsuites>
    <testsuite name="money">
      <directory>tests</directory>
    </testsuite>
  </testsuites>
</phpunit>
以下のように--testsuiteで試験対象を指定して実行する。
phpunit --bootstrap src/autoload.php --testsuite money
===Assertions===
Ref:
*[https://docs.phpunit.de/en/9.6/assertions.html 1. Assertions — PHPUnit 9.6 Manual].
*[https://docs.phpunit.de/en/9.6/writing-tests-for-phpunit.html?highlight=assertSame#testing-exceptions 2. Writing Tests for PHPUnit — PHPUnit 9.6 Manual]
基本はassertSameでテストすればいいのだが、それ以外にも例外とかいろいろ試験したいケースがあるので、メソッドを整理する。
====Exception====
特に例外の試験がイレギュラー。
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class ExceptionTest extends TestCase
{
    public function testException(): void
    {
        $this->expectException(InvalidArgumentException::class);
        // Run test target code following.
    }
}
上記のようにexpectExceptionを使う。
*expectException:
*expectExceptionCode:
*expectExceptionMessage:
*expectExceptionMessageMatches:
例外が発生する処理の前に記述しておく。
====Output====
echoなど標準出力を試験する際も専用のメソッドがある。
*<code>void expectOutputRegex(string $regularExpression)</code>
*<code>void expectOutputString(string $expectedString)</code>
*<code>bool setOutputCallback(callable $callback)</code>
*<code>string getActualOutput()</code>
expectExceptionと同様に事前にセットしておく。
===Command-Line===
Ref: [https://docs.phpunit.de/en/9.6/textui.html 3. The Command-Line Test Runner — PHPUnit 9.6 Manual].
 
phpunitコマンドでいろいろできる。いくつか重要なオプション、使用方法がある。
*phpunit file.php: 指定したファイルのテストを実行。
*--testsuite <name>: テストを指定。
===Test Doubles===
Ref: [https://docs.phpunit.de/en/9.6/test-doubles.html 8. Test Doubles — PHPUnit 9.6 Manual].
 
テスト時に、依存関係を模擬したもので置換したいことがある。PHPUnitにそういう仕組が用意されている。
 
stub=親、mock=子。
 
メソッド内で他のクラス・メソッドを使う場合はmockで対象クラスを模擬させる。<syntaxhighlight lang="php">
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
 
final class SubjectTest extends TestCase
{
    public function testObserversAreUpdated(): void
    {
        // Create a mock for the Observer class,
        // only mock the update() method.
        $observer = $this->createMock(Observer::class);
 
        // Set up the expectation for the update() method
        // to be called only once and with the string 'something'
        // as its parameter.
        $observer->expects($this->once())
                ->method('update')
                ->with($this->equalTo('something'));
 
        // Create a Subject object and attach the mocked
        // Observer object to it.
        $subject = new Subject('My subject');
        $subject->attach($observer);
 
        // Call the doSomething() method on the $subject object
        // which we expect to call the mocked Observer object's
        // update() method with the string 'something'.
        $subject->doSomething();
    }
}
 
</syntaxhighlight>基本的な作り。
#createMock(<class>::class)で該当クラスのモックを作成。
#expectsに呼出回数条件のオブジェクトをセット。
#methodで対象メソッドを指定。
#withで、該当メソッドの引数処理を指定。
デフォルトで模擬実装はnullを返す。戻り値を変更したければ、will($this->returnValue())などを指定する。よく使うので短縮記法もある。
{| class="wikitable"
|+Table 8.1 Stubbing short hands
!short hand
!longer syntax
|-
|<code>willReturn($value)</code>
|<code>will($this->returnValue($value))</code>
|-
|<code>willReturnArgument($argumentIndex)</code>
|<code>will($this->returnArgument($argumentIndex))</code>
|-
|<code>willReturnCallback($callback)</code>
|<code>will($this->returnCallback($callback))</code>
|-
|-
|GET
|<code>willReturnMap($valueMap)</code>
|<code>/photos/{photo}/edit</code>
|<code>will($this->returnValueMap($valueMap))</code>
|edit
|photos.edit
|-
|-
|PUT/PATCH
|<code>willReturnOnConsecutiveCalls($value1, $value2)</code>
|<code>/photos/{photo}</code>
|<code>will($this->onConsecutiveCalls($value1, $value2))</code>
|update
|photos.update
|-
|-
|DELETE
|<code>willReturnSelf()</code>
|<code>/photos/{photo}</code>
|<code>will($this->returnSelf())</code>
|destroy
|-
|photos.destroy
|<code>willThrowException($exception)</code>
|}
|<code>will($this->throwException($exception))</code>
create/editは新規作成、編集用の画面表示。実際の処理はstore/updateで行う。
|}willReturnCallbackで呼び出し関数をまるごと別のものに置換できる。これが非常に便利。
===Topic===
====Test private/protected====
Ref:
*[https://stackoverflow.com/questions/249664/best-practices-to-test-protected-methods-with-phpunit php - Best practices to test protected methods with PHPUnit - Stack Overflow]
*[https://zenn.dev/ttskch/articles/c7dcd5c1188cdd PHPUnitでprivateメソッドをテストする]
*[https://qiita.com/ponsuke0531/items/6dc6fc34fff1e9b37901 privateとprotectedメソッドをPHPUnitでテストする方法 #PHP - Qiita]
クラスのprivate/protectedメソッドのテストには工夫が必要となる。<syntaxhighlight lang="php">
    /**
    * privateメソッドを実行する.
    * @param string $methodName privateメソッドの名前
    * @param array $param privateメソッドに渡す引数
    * @return mixed 実行結果
    * @throws \ReflectionException 引数のクラスがない場合に発生.
    */
    private function doMethod(string $methodName, array $param)
    {
        // テスト対象のクラスをnewする.
        $controller = $this->instance;
        // ReflectionClassをテスト対象のクラスをもとに作る.
        $reflection = new \ReflectionClass($controller);
        // メソッドを取得する.
        $method = $reflection->getMethod($methodName);
        // アクセス許可をする.
        $method->setAccessible(true);
        // メソッドを実行して返却値をそのまま返す.
        return $method->invokeArgs($controller, $param);
    }
</syntaxhighlight>ReflectionClassを使って取得できる。上記の関数のFormController部分を試験対象のクラスに差し替えればOK。$this->instanceを指定しておけばそのまま流用できるか。getProperty/getValueでprivateプロパティーも取得可能。
====Test header====
Ref: [https://stackoverflow.com/questions/9745080/test-php-headers-with-phpunit unit testing - Test PHP headers with PHPUnit - Stack Overflow].
 
header関数を使用する場合、phpunitの標準出力と干渉して以下のエラーが出て試験できない。
Cannot modify header information - headers already sent by (output started at .../vendor/phpunit/phpunit/src/Util/Printer.php:138)
回避方法が2種類ある。
#<code>@runInSeparateProcess</code>
#phpunit --stderr
1個目のアノテーションをテストメソッドに指定すると別プロセスでの実行になる。ただ、プロセス生成は時間がかかるため、試験が多いと効率が悪い。
 
2個目のphpunitの出力を標準エラーに出力させる方法がシンプルで効率もいい。phpunit.xmlに stderr="true"を指定するとキー入力を省略できる。こちらで対応しよう。
====Test exit====
Ref:
*[https://qiita.com/kumagaias/items/5b1d95a897bae11f2a5a PHP でテストコードを意識したコーディング #PHPUnit - Qiita]
*[https://qiita.com/tenkoma/items/1ac9625b4233c5893812 echo + exit しているPHPコードをユニットテストで保護しながら改善する #PHP - Qiita]
*[https://uzulla.hateblo.jp/entry/2019/06/27/193210 header後にdieするテストのアンチパターン - uzullaがブログ]
*[https://stackoverflow.com/questions/23915434/ignore-exit-and-die-with-phpunit php - Ignore exit() and die() with PHPUnit - Stack Overflow]
*[https://stackoverflow.com/questions/1347794/how-do-you-use-phpunit-to-test-a-function-if-that-function-is-supposed-to-kill-p unit testing - How do you use PHPUnit to test a function if that function is supposed to kill PHP? - Stack Overflow]
header()後のexit()など、exit/dieを使用するコードがある。phpunit内でこれらがあると、テストも強制終了になる。
 
上記の別プロセスで実行していた場合、以下のエラーになる。
Test was run in child process and ended unexpectedly
対処方法がいくつかある。
#exitを使わないコードに変更。
#isTestのようなフラグを元コードに入れてテスト可否で分岐してexitを回避。
#execで外部プロセスで実行してexitCodeを試験。
#exit/die部分だけ別関数に抽出してmockで置換?
<https://notabug.org/gnusocialjp/gnusocial/src/main/actions/apiaccountregister.php> のclientErrorが内部でexitする。
 
このclientErrorをwillなどで置換すればよさそう?
==Language Reference==
===Types===
====Introduction====
Ref: [https://www.php.net/manual/en/language.types.intro.php PHP: Introduction - Manual].
 
PHPの変数は以下の型のいずれかの値となる。
*null
*bool
*int
*float (floating-point number)
*string
*array
*object
*callable
*resource
C言語のようなlong/doubleのような精度ごとの型はない。
 
==== System ====
[https://www.php.net/manual/ja/language.types.type-system.php PHP: 型システム - Manual]
 
組込型の他に、ユーザー定義の型、aliasなどいくつかの型がある。
 
===== 基本型 =====
言語に統合されていて、ユーザー定義で再現不能。


==== Requests ====
* 組込
[https://laravel.com/docs/5.8/requests HTTP Requests - Laravel 5.8 - The PHP Framework For Web Artisans]
** null
** スカラー型: bool/int/float/string
** array
** object
** resource
** never
** void
** クラス内の相対型: self/parent/static
* Value型: false/true
* ユーザー定義型/クラス型
** インターフェイス
** クラス
** 列挙型
* callable


===== Retrieving Input =====
===== 複合型 =====
複数の基本型を組み合わせた型。交差型とunion型で作れる。


====== Old Input ======
* 交差型: 宣言した複数のクラス型をすべて満たす型。&で表現。T/U/Vの交差型はT&U&Vと書く。
Laravelは次のリクエストの送信完了まで、前回のリクエストの入力を保存している。これにより、バリでーしょねらー時などに入力内容を復元できる。
* union型: 複数の型を受け入れる型。|で表現。T/U/Vのunion型はT|U|Vと書く。交差型を含む場合、T|(X&U)と丸括弧で囲む必要がある。


====== Retrieving Old Input ======
===== alias =====
Request$ondにより、セッションから前回の入力を取得できる。
PHPはmixedとiterableの2個の型のエイリアスに対応している。
$username = $request->old('username');
Bladeでも使える。フォーム要素の前回の値表示として使える。基本は指定するとよいだろう。
<nowiki><input type="text" name="username" value="{{ old('username') }}"></nowiki>


==== Views ====
* mixed=object|resource|array|string|float|int|bool|null: PHP 8.0.0で導入。mixedは型のトップ。他の全部の型はこの型の部分になる。
* iterable=Traversable|array: PHP 7.1.0で導入。foreachで反復可能でジェネレーター内でyield from可能。


* [https://laravel.com/docs/5.8/views#view-composers Views - Laravel 5.8 - The PHP Framework For Web Artisans]
ただし、ユーザー定義のエイリアスは未対応。
* [https://laravel.com/docs/5.8/helpers#method-view Helpers - Laravel 5.8 - The PHP Framework For Web Artisans]
* [https://biz.addisteria.com/laravel_view_composer/ Laravelで現在のユーザーを全ページで共有したい時はview composerが便利 | 40代からプログラミング!]
* [https://laravel.com/api/6.x/Illuminate/View/View.html Illuminate\View\View | Laravel API]


ControllerからViewにデータを渡す際、user情報など毎回渡すデータがあったりする。そういうのをControllerとは別の場所で自動処理する仕組みがView Composer。Controllerの処理がすっきりする。
====Boolean====
*[https://www.php.net/manual/ja/language.types.boolean.php PHP: 論理型 (boolean) - Manual]
*[https://qiita.com/minato-naka/items/50645a45998e91c83e2b PHP trueになるもの・falseになるもの確認 falseを入れた配列はどっちに? #初心者 - Qiita]
条件判定にかかってくるので非常に重要。


ViewはHTMLを保持している。view関数で、テンプレートを指定して、テンプレートで使用する変数を渡せば、Viewインスタンスを取得する。ViewインスタンスがBladeのHTMLを保有している。
まずは、下記のfalseになるもの一覧を把握し、それ以外はすべてtrueになるということを把握しておく。
*booleanのfalse
*intの0
*floatの0.0
*stringの空文字列、"0"
*要素数0個のarray
*null (未初期化変数含む)
stringの"0"と要素0のarrayがfalseになる点が重要。注意する。要素0のarrayは包含判定、検索などでよく使う。


いくつかViewインスタンスのメソッドがある。
stringの0ははまりどころ。stringは何がくるかわからないなら、strlenで文字数を見たほうが確実。
====Strings====
Ref: [https://www.php.net/manual/ja/language.types.string.php PHP: 文字列 - Manual].


first: 指定した配列の最初のテンプレートを表示に使う。基本的にアプリなどでユーザーが上書きするよう。あまり使わない。
非常に重要。
  return view()->first(['custom.admin', 'admin'], $data);
=====Literal=====
文字列リテラルとしては4の表現がある。
*Single quote: <nowiki>''</nowiki> 変数展開されない。
*Double quote: "" 変数展開される。
*Here document: <<<EOT 二重引用符扱いで変数展開される。
*Nowdoc: <<<'EOT' 一重引用符扱いで変数展開されない。
引用符内で引用符'を使う場合はバックスラッシュ\でエスケープが必要。バックスラッシュ自体の指定は二重\\。


  return view('greetings', ['name' => 'Victoria']);
Here document/Nowdocは終端IDのインデントで行頭を識別しており、インデントに意味があるので注意する。
  return view('greeting')->with('name', 'Victoria');  
echo <<<END
引数で渡すほかに、with関数でも渡せる。
      a
      b
    c
  \n
  END;


==== Validation ====
echo <<<'EOT'
主にPOST系のアクション実行用に、バリデーションというデータの検証の仕組みがある。
My name is "$name". I am printing some $foo->foo.
Now, I am printing some {$foo->bar[1]}.
This should not print a capital 'A': \x41
EOT;


ControllerのValidateRequestsトレイトに用意されており、Controllerのメソッドとして使える。
=====Parse=====
$this->validate($request, [検証設定の配列]);
二重引用符とヒアドキュメントではエスケープシーケンスが解釈され、変数が展開される。<syntaxhighlight lang="php">
上記の書式で使う。
<?php
$juice = "apple";


ただ、この基本的なバリデーションだとコントローラーに都度記載が必要。できれば、リクエストなどで別でやりたい。
echo "He drank some $juice juice." . PHP_EOL;


FormRequestという仕組みがある。フォームに関する機能をリクエストに組み込む。コントローラーではなく、リクエストでフォーム処理をやってくれる。
// 意図しない動作をします。"s" は、変数名として有効な文字です。よって、変数は $juices を参照しています。$juice ではありません。
echo "He drank some juice made of $juices." . PHP_EOL;


FormRequestを派生させたクラスを作成し、適用するURLパスとルールを定義。使用するコントローラーのアクションの引数に、Reqeustではなく作ったFormRequest派生クラスに変更するとOK。これだけ。
// 参照する変数名を波括弧で囲むことで、変数名の終端を明示的に指定しています。
echo "He drank some juice made of {$juice}s.";


エラーメッセージもFormRequest派生クラスで作れる。
//
$juices = array("apple", "orange", "koolaid1" => "purple");
echo "He drank some $juices[0] juice.".PHP_EOL;
echo "He drank some $juices[1] juice.".PHP_EOL;
echo "He drank some $juices[koolaid1] juice.".PHP_EOL;


==== Logging ====
// 複雑な例1
[https://laravel.com/docs/5.8/logging Logging - Laravel 5.8 - The PHP Framework For Web Artisans]


===== Other =====
// これが動作しない理由は、文字列の外で $foo[bar]
// が動作しない理由と同じです。
// PHP はまず最初に foo という名前の定数を探し、
// 見つからない場合はエラーをスローします。
// 定数が見つかった場合は、その値('foo' そのものではない)
// を配列のインデックスとして使います。
echo "This is wrong: {$arr[foo][3]}";


* [https://stackoverflow.com/questions/41978290/how-to-log-object laravel - How to Log object? - Stack Overflow]
// 動作します。多次元配列を使用する際は、
* [https://qiita.com/ucan-lab/items/29614d0f3ded1d3a94fb 【超入門】Laravelのデバッグ手法22選 #PHP - Qiita]
// 文字列の中では必ず配列を波括弧で囲むようにします。
echo "This works: {$arr['foo'][3]}";


Laravelで便利なログ出力方法がいくつかある。
// 複雑な例。二重展開で変数になる場合だけ式が使える模様。
$var1=9;
echo "{${mb_strtolower('VAR1')}}"; // 9


* ヘルパー関数 ([https://laravel.com/docs/5.8/helpers Helpers - Laravel 5.8 - The PHP Framework For Web Artisans])
?>
** dd: dump and die。引数の変数をその場で表示して終了。
</syntaxhighlight>波括弧はなくてもいいが、文字列が連結するなどして変数名の終端を区別できない場合に必須になる。
** dump: 引数の変数をその場で表示。
* Log::debug(): ログファイルstorage/logsに出力 (<code>use Illuminate\Support\Facades\Log;</code>)。
** <code>Log::info(print_r($user, true));</code>
** <code>Log::info(json_encode($user));</code>
** オブジェクト類はjson_encodeがいい。


====== Allowed memory size of 134217728 bytes exhausted (tried to allocate 90181632 bytes) ======
特に重要な挙動は以下。
なお、print_r($request, true) などをすると、以下のエラーが出る。
[2017-09-06 15:19:44] production.ERROR: Symfony\Component\Debug\Exception\FatalErrorException: Allowed memory size of 134217728 bytes exhausted (tried to allocate 90181632 bytes) in [path to file reducted] Stack trace: #0 {main}
[[https://laracasts.com/discuss/channels/requests/print-rrequest-causes-out-of-memory-error print_r($request) causes out of memory error.]] にあるように、print_rは継承元も再帰的に出力し、Laravelはたくさん継承しているからいっぱいになるらしい。


これはせずに、dumpなどを使う。
* ""内だと、連想配列添字の引用符不能。
* ${}内だと、連想配列添字の引用符必要。


複雑な形式は{$ ... }がセット。{$ } 部分で変数が式扱いになる。


<nowiki>さらに複雑なことができる。{${}}を指定すると、内側の波括弧内で、${}部分が変数評価になる場合にだけ式を指定できる。動きがトリッキーすぎる。フォーマット文字列的なことには使えない。バグのもとになりそうなので使用を控えたほうがよさそう。</nowiki>
=====Format=====
Ref:
*[https://chaika.hatenablog.com/entry/2023/02/14/120000 PHP 文字列中に変数で値を埋め込むやつのメモ - かもメモ]
*[https://qiita.com/iez/items/8a36e5cba8bf6af21f45 python の string.format が便利だからPHPでも使いたい #PHP - Qiita]
PHPにはPythonのformatメソッド相当はない。が似たような目的の関数がある。
*sprintf/vprintf
*strtr
strtrは第2引数にold => newの置換のペアの配列を渡す。やることは同じようなものだけどちょっと違う。


===== debugbar =====
vsprintfは置換対象が可変長引数ではなく配列なだけ。
=====sprintf=====
Ref: [https://www.php.net/manual/ja/function.sprintf.php PHP: sprintf - Manual]


* [https://github.com/barryvdh/laravel-debugbar barryvdh/laravel-debugbar: Debugbar for Laravel (Integrates PHP Debug Bar)]
今後何度も使う。
* [https://qiita.com/goto_smv/items/b7be0985029ab3d03217 Laravel Debugbarについて #Laravel - Qiita]


* [https://qiita.com/rarrrrsQ/items/34f3867bbcb17604e136 Laravelでのデバッグ方法4パターンまとめ #初心者 - Qiita]
C言語のprintfといろいろ違うところがある。<syntaxhighlight lang="php">
<?php
$format = 'The %2$s contains %1$d monkeys.
          That\'s a nice %2$s full of %1$d monkeys.';
echo sprintf($format, $num, $location);


DebugbarというLaravelの開発デバッグにかなり便利なツールがある。
echo sprintf("%'.9d\n", 123); // ......123
echo sprintf("%'.09d\n", 123); // 000000123


導入方法
?>
composer require barryvdh/laravel-debugbar --dev
</syntaxhighlight>特徴的なのが`%数$指定子`で引数の番号を選べるところ。Pythonの`{数:指定子}`に似ている。
.envでAPP_DEBUG=trueの場合に機能する。


クエリーの発行数、メモリー使用量、実行時間。このあたりが特に重要と思われる。
後は埋める文字を指定する際は'を前置。
=====文字列の切り出し=====
いくつか方法がある。
*substr/mb_substr
*strpos/mb_strpos/strrpos/mb_strrpos ([https://www.php.net/manual/ja/function.strpos.php PHP: strpos - Manual])
*split
*preg_match ([https://www.php.net/manual/ja/function.preg-match.php PHP: preg_match - Manual])
preg_matchの自由度が高い。速度を気にしなくていいならこれでいいと思われる。ただ、引数の配列に入ってくるのがいまいち。関数の戻り値でほしい。


=== Frontend ===
strposとsubstrを組み合わせると端の文字列を切り出せる。
=====文字列置換=====
*str_replace
*substr_replace: 日本語不能。
*preg_replace
*explode/implode
substr_replace($text, <nowiki>''</nowiki>, -1);
substr_replace($text, '.', nb_strrpos('_'));
1文字などの置換ならmb_strrposとの組み合わせ。
$query = $request->query();
foreach ($query as $key => $value) {
unset($query[$key]);
$keys = explode('_', $key);
$key = implode('_', array_slice($keys, 0, -1)) . '.' . $keys[count($keys)-1];
$query[$key] = $value;
}
explode/implodeが無難で確実。
=====startsWith/endsWith=====
PHP 8なら「[https://www.php.net/manual/en/function.str-starts-with.php PHP: str_starts_with - Manual]/[https://www.php.net/manual/en/function.str-ends-with.php PHP: str_ends_with - Manual]」がある。
 
PHP 8未満なら以下のようなコード。
function startsWith( $haystack, $needle ) {
      $length = strlen( $needle );
      return substr( $haystack, 0, $length ) === $needle;
}
function endsWith( $haystack, $needle ) {
    $length = strlen( $needle );
    if( !$length ) {
        return true;
    }
    return substr( $haystack, -$length ) === $needle;
}
mb_strlen/mb_substrでマルチバイト対応。


==== Blade Templates ====
===== 改行分割 =====
[https://laravel.com/docs/5.8/blade Blade Templates - Laravel 5.8 - The PHP Framework For Web Artisans]


===== @section/@yield: Template Inheritance =====
* [https://stackoverflow.com/questions/3997336/explode-php-string-by-new-line Explode PHP string by new line - Stack Overflow]
Bladeでは、継承とセクションの2種類のテンプレートの流用方法がある。
* [https://stackoverflow.com/questions/1483497/split-string-by-new-line-characters php - Split string by new line characters - Stack Overflow]
* [https://stackoverflow.com/questions/7836632/how-to-replace-different-newline-styles-in-php-the-smartest-way How to replace different newline styles in PHP the smartest way? - Stack Overflow]


Layoutはページ全体のテンプレート。セクションは区画単位のテンプレート。
<code>explode('\n', $csv)</code> のようなことをしたくなるが、改行が\nとは限らない。
$array = preg_split('/\R/u', $string);
上記がいい。\Rが\r \n \n\rなどにマッチ。uで入力がUTF-8の場合を考慮。例えば、「腰」がuをつけないと分割されてしまう。


セクションは@section/@yieldを使って実現する。
===== trim =====
[https://www.php.net/manual/ja/function.trim.php PHP: trim - Manual]


@sectionは@showか@endsectionで終わる。
文字列の両端のホワイトスペースを除去する。


@show/@endsectionの違い ([https://qiita.com/sirogane/items/1829c2a12d284ae20eb5 Bladeテンプレートの@showと@endsectionを間違えないようにする #Laravel - Qiita])。親テンプレートで定義する場合は@show。親でも@endsectionを使うと、sectionの内容が消える。sectionは本来、元テンプレートから継承したものを埋め込むためのものなので、ベーステンプレートだと埋め込み元がないので消えるのだと思う。だから、@showを使う必要があると思われる。
====Array====
=====Create=====
配列の作成方法がいくつかある。
*array()/[]
*explode ([https://www.php.net/manual/ja/function.explode.php PHP: explode - Manual])


===== @component/@slot: Components & Slots =====
===== Read =====
テンプレートよりも細かい部品単位の流用方法がcomponent。ヘッダーやフッター、ボタンなどの部品単位で流用できる。


viewsディレクトリー以下に格納する。一般的にはviews/components/ok.blade.phpなどのように配置し、components.okなどで、コンポーネント名を指定して読み込む。
====== 末尾要素 ======
[https://hishikiryu.com/php-get-last-array-value/ 【PHP】配列の最後(末尾)の要素を取得まとめ array_key_last, count, end関数 | ヒシキリュウ.com]


component定義側は、通常のBladeと同じだが、コンポーネント内で変数のプレースホルダーを使用できる。これは、利用側の@slotのブロックで引き渡す。
* array_key_last: PHP v7.3.0+ ($arr[array_key_last($arr)];)。
<!-- components/message.blade.php -->
* count: 昔ながら ($arr[count($arr) - 1];)
<nowiki><div class="message">
* end: 非推奨。
          <p class="msg_title">{{$msg_title}}</nowiki><nowiki></p></nowiki>
  <nowiki><p class="msg_content">{{$msg_content}}</nowiki><nowiki></p></nowiki>
<nowiki> </nowiki><nowiki>{{$slot}}</nowiki>
<nowiki></div></nowiki>
component利用側で組み込むために工夫する。
@component(名前)
  @slot('msg_title')
    title
  @endslot
    <nowiki><strong>Whoops!</strong></nowiki> Something went wrong!
@endcomponent
$slot変数には、@componentsのテキスト、@slotブロック以外が入る。


Laravelに複数の在不明のcomponentを順番に適用させたい場合componentFirstを使う。
=====Append=====
  @componentFirst(['custom.alert', 'alert'])
配列要素の追加。
    <nowiki><strong>Whoops!</strong></nowiki> Something went wrong!
$arr[キー] = 値;
@endcomponent
  $arr[] = 値;
@slot以外に、変数を渡すこともできる。
  @component('alert', ['foo' => 'bar'])
array_push($arr, 'a', 'b'); // array_pushだと一度に複数追加できる。
    ...
  array_unshift($arr, 'a', 'b'); // 先頭に追加。
  @endcomponent
array_merge($arr, [0, 1]); // 配列同士の追加。
$arr = [...$arr, ...[0, 1]] // PHP7.4以上。...演算子。性能はarray_mergeのほうが高い。
基本は$arr[キー] $arr[]でいいだろう。
=====Remove=====
配列要素の削除方法がいくつかある。
*unset($arr[$key]);
*array_shift($arr): 先頭要素を削除。削除済み要素を返す。破壊的な処理。
*array_pop($arr): 末尾要素を削除。削除済み要素を返す。破壊的な処理。
*array_slice ([https://www.php.net/manual/ja/function.array-slice.php PHP: array_slice - Manual]): 先頭・末尾の要素を除去した要素を返す。
$ar = [0, 1, 2];
foreach($ar as $e) {
    echo $e;
    if ($e === 1) {
        array_shift($ar);   
    }
  }
   
   
  @component('alert')
  print_r($ar);
  @slot('foo', 'bar')
@endcomponent
slotの設定方法は複数ある。@slot/@endslotよりかは@slot()で設定するほうが短い。が、@component内は$slotのデフォルト値を入れるとしたほうがわかりやすいかもしれない。


ただし、@endcomponentは省略できない。
012Array
(
    [0] => 1
    [1] => 2
)
途中で削除しても、foreachは詰めたりしない。
=====Copy=====
[https://stackoverflow.com/questions/6418903/how-to-clone-an-array-of-objects-in-php How to clone an array of objects in PHP? - Stack Overflow]


名前付きslotのデフォルト値設定はない。やりたければ、??や@if/@issetで自分でやっておく。
配列変数を代入すると通常はそれでコピーになる。ただし、配列にオブジェクトがあると、そのオブジェクトはシャローコピーになる。
$new = array();
foreach ($old as $k => $v) {
    $new[$k] = clone $v;
}
上記のように配列要素をcloneでコピーして作る必要がある模様 ([https://www.php.net/manual/ja/language.oop5.cloning.php PHP: オブジェクトのクローン作成 - Manual])。
=====Comma=====
*[https://www.php.net/manual/ja/language.types.array.php PHP: 配列 - Manual]
*[https://kinsta.com/jp/blog/php-7-3/#trailing-comma-in-function-calls PHP 7.3の新機能(Kinstaで利用可能)]
PHPでは配列の終端カンマは許容される。


デフォルトの$slotは、@componentの配列式 (@component(, ['slot']) では指定できないが、view関数で呼ぶ際は['slot']で指定できる。
他にも、名前空間のグループ指定はPHP7.2以上、関数の引数はPHP7.3以上で可能になった。
=====連想配列判定=====
[https://qiita.com/Hiraku/items/721cc3a385cb2d7daebd 配列か連想配列か判定する #PHP - Qiita]
<?php
if (array_values($arr) === $arr) {
  echo '$arrは配列';
} else {
  echo '$arrは連想配列';
}
これで添え字が、数字かどうかをみるのがいい模様。
=====Convert=====
======連想配列→単純配列======
associative arrayをsimple arrayに変換する。


基本は短くできるので、component内では$slotを使うほうがいい。
[https://stackoverflow.com/questions/15191903/convert-an-associative-array-to-a-simple-array-of-its-values-in-php Convert an associative array to a simple array of its values in php - Stack Overflow]


なお、$slotは扱いに注意が必要。使う側で指定がなかったら、nullではないが見えない変な値が入っている。変数として使う際は"$slot"のように二重引用符で囲んで、値がない場合に確実に空にしておく。
[https://qiita.com/ktz_alias/items/16241bdf11757b556cdb 連想配列をキーと値のペアの配列にするちょっと気のきいた方法(かも) #PHP - Qiita]
*$array = array_values($array);: 値だけを1次元にしたい場合。
*array_map(null, array_keys($a1), array_values($a1));: 連想配列の[[key,value], [key2, valu2]] 形式。
後者のパターンはそれなりに使う気がする。
======単純配列→連想配列======
[https://www.techiedelight.com/ja/convert-regular-array-to-associative-array-php/ PHP で通常の配列を連想配列に変換する]


===== @include: Including Sub-Views =====
いくつか方法がある。
レイアウトやコンポーネントのように、変数の引き渡しなど複雑なことをしない場合、単純な定形固定文字列を読み込むような場合、Sub-Viewsというのを使うこともできる。
#array_combine
#array_fill_keys
#foreach
#array_flip
$ar = ['a', 'b'];
$ar2 = array_combine($ar, $ar);
var_dump($ar2);
array_combineがシンプル。array_fill_keysは0初期化などしたい場合。


これは@includeでテンプレートファイルをそのまま読み込むのが基本。親の変数もそのまま使える。他に、引数で変数を渡すこともできる。
====== CSV→連想配列 ======
@include('view.name', ['some' => 'data'])
@includeIf('view.name', ['some' => 'data'])
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])
@includeで指定したテンプレートが不在の場合、Laravelはエラーを投げる。このエラーを回避したい場合、@includeIf指令を使う。@includeIfのバリエーションの一種で、配列のテンプレート名で存在する最初のものを使う場合、@includeFirstを使う。


条件がtrueなら読み込む場合、@includeWhenがある。
* [https://medium.com/@czmole/php-convert-csv-to-associative-arrays-b82b9b4d4412 PHP convert CSV to associative arrays | by Catalin ZMOLE 👨‍💻 | Medium]
@include($boolean, 'view.name', ['some' => 'data'])
* [https://steindom.com/2012/12/08/shortest-php-code-convert-csv-associative-array Shortest PHP code to convert CSV to associative array | Steindom]
@ifよりもシンプル。
* [https://www.php.net/manual/en/function.str-getcsv.php PHP: str_getcsv - Manual]


[https://qiita.com/ah__memo/items/1936419d908875477aa8 【Laravel】bladeの@includeと@componentの違い #PHP - Qiita]
CSVを、よくDBの取得結果の形式の、行単位連想配列に変換する。方法がいくつかある。
<?php
$csv = array_map('str_getcsv', file($file));
if (count($csv) && !count($csv[count($csv)-1])) unset($csv[count($csv)-1]);
array_walk($csv, function(&$a) use ($csv) {$a = array_combine($csv[0], $a);});
array_shift($csv); # remove column header
?>


includeはcomponentと違って@endincludeがいらない。
$rows = array_map('str_getcsv', file('myfile.csv'));
$header = array_shift($rows);
$csv = array();
foreach ($rows as $row) {
  $csv[] = array_combine($header, $row);
}
1番目の方法がシンプル。


componentはslotで渡すこともできる。$slotを使える点が大きな違い。
=====Search=====
[https://www.sejuku.net/blog/22098 【PHP入門】配列の値を検索するarray_searchと他4つの関数 | 侍エンジニアブログ]


複雑で長いHTMLなどを、引き渡して使いたい場合、componentのほうがいい。
いくつか方法がある。


===== @each: Rendering Views For Collections =====
基本はin_array。
意外と使用頻度が高いのが繰り返し表示。例えば、リストの項目、テーブルの行など。これようの指令が@each
======array_key_exists/キー確認======
  @each('components.item', $data, 'item');
[https://www.php.net/manual/ja/function.array-key-exists.php PHP: array_key_exists - Manual]
$data配列の要素をコンポーネントのitem変数に渡す。
// components/item.blade.php
<nowiki><li>{{$item['name']}}</nowiki> <nowiki>[{{$item['mail']}}]</nowiki><nowiki></li></nowiki>


===== Displaying Data =====
配列のキーの存在確認のほうほうがいくつかある。
*array_key_exits: array_key_exists('first', $search_array);
*isset: nullだとfalseになる (isset($search_array['first']))。
*empty: nullだとfalseになる。
*??: キー不在だとnullになるのでこれでない場合に対応できる。
基本はarray_key_exitsか??。$ar ?? nullでWARNINGを回避しながら手短にかける。
======空確認======
[https://qiita.com/miriwo/items/c4760cbb2807ee84ef2d PHP 配列が空かどうかを判定する #初心者 - Qiita]


====== Displaying escaped Data ======
emptyで確認できる。が、単にnullなどの場合も判定してしまう。null or emptyという意味ならemptyでもOK。
Bladeでデータを表示する際は、二重波括弧を使う。
Route::get('greeting', function () {
    return view('welcome', ['name' => 'Samantha']);
});


Hello, <nowiki>{{ $name }}</nowiki>.
逆に、issetであることと、nullではないことを確認できる。
二重波括弧は自動的にhtmlspecialcharsでXSS対策してくれる。変数の他に、PHP関数の結果も表示できる。
======array_search======
array_search() - 指定した値を配列で検索し、見つかった場合に対応する最初のキーを返す


====== Displaying Unescaped Data ======
全てのキーが必要なら、array_keysにfilter_valueを指定する。
なお、生のHTMLなどのデータをそのまま表示させたい場合、二重波括弧の代わりに<code>{!! !!}</code>で囲む。
======in_array======
Hello, {!! $name !!}.
in_array — 配列に値があるかチェックする
Hello, {!! e($name) !!}.
in_array([[/wiki.gnusocial.jp//www.php.net/manual/ja/language.types.mixed.php|mixed]] <code>$needle</code>, [[/wiki.gnusocial.jp//www.php.net/manual/ja/language.types.array.php|array]] <code>$haystack</code>, [[/wiki.gnusocial.jp//www.php.net/manual/ja/language.types.boolean.php|bool]] <code>$strict</code> = <code>[[/wiki.gnusocial.jp//www.php.net/manual/ja/reserved.constants.php#constant.false|false]]</code>): [[/wiki.gnusocial.jp//www.php.net/manual/ja/language.types.boolean.php|bool]]
もっというと、e()でエスケープしておくと安心 ([https://qiita.com/itty-star/items/22b4293cdbd847a1aa70 Bladeで変数に入れたhtml文字列を表示させる #Laravel - Qiita])。
<code>haystack</code> 内の <code>needle</code> を検索します。 <code>strict</code> が設定されていない限りは型の比較は行いません。


[https://laravel.com/docs/5.8/helpers Helpers - Laravel 5.8 - The PHP Framework For Web Artisans]
基本は$strict=trueで指定したほうがいい。完全一致検索。
======any/all/some/every======
[https://stackoverflow.com/questions/39875691/is-there-a-php-equivalent-of-javascripts-array-prototype-some-function Is there a PHP equivalent of JavaScript's Array.prototype.some() function - Stack Overflow]


混乱するが、二重波括弧内はPHP扱い、外はHTML扱い。二重波括弧内で表示文字列扱いしたいなら、文字列にする必要がある。
配列に対する、1個または全部の評価。


===== Blade & JavaScript Frameworks =====
JavaScriptのsome/every相当。
JavaScriptフレームワークの中に、二重波括弧をプレースホルダーとしてそのまま使うものがある。そういう場合、@を波括弧に前置するとそのまま表示する。
<nowiki><h1>Laravel</h1></nowiki>
<nowiki> </nowiki>
Hello, @<nowiki>{{ name }}</nowiki>.


====== The @verbatim Directive ======
PHP 8.4ならarray_any/array_allが存在する。
JavaScript変数を表示する場合、波括弧の前に@をたくさん置かないで済むように、@verbatimで囲める。
@verbatim
<nowiki> </nowiki>  <nowiki><div class="container">
                      Hello, {{ name }}</nowiki>.
<nowiki> </nowiki>  <nowiki></div></nowiki>
@endverbatim


===== Control Structure/Blade Directives =====
PHP 8.4未満なら、いくつか方法がある。
function array_any(array $array, callable $fn) {
    foreach ($array as $value) {
        if($fn($value)) {
            return true;
        }
    }
    return false;
}
function array_every(array $array, callable $fn) {
    foreach ($array as $value) {
        if(!$fn($value)) {
            return false;
        }
    }
    return true;
}


====== If Statements ======
  function array_some(array $data, callable $callback) {
  @if (count($records) === 1)
    $result = array_filter($data, $callback);
     I have one record!
     return count($result) > 0;
@elseif (count($records) > 1)
  }
    I have multiple records!
@else
    I don't have any records!
  @endif
   
   
  @unless (Auth::check())
  $myarray = [2, 5, 8, 12, 4];
     You are not signed in.
array_some($myarray, function($value) {
  @endunless
     return $value > 10;
  }); // true
@isset($records)
foreachで途中で終わるほうが速い模様。
    // $records is defined and is not null...
======配列同士の包含・交差判定======
  @endisset
*[https://stackoverflow.com/questions/9655687/check-if-an-array-contains-all-array-values-from-another-array php - Check if an array contains all array values from another array - Stack Overflow]
 
*[https://stackoverflow.com/questions/523796/checking-if-any-of-an-arrays-elements-are-in-another-array php - Checking if ANY of an array's elements are in another array - Stack Overflow]
  @empty($records)
1個でも入っているかを見たければ、array_intersect ([https://www.php.net/array_intersect PHP: array_intersect - Manual]) がこの目的に合致する。
    // $records is "empty"...
  $peopleContainsCriminal = !empty(array_intersect($people, $criminals));
@endempty
  $peopleContainsCriminal = array_intersect($people, $criminals);
$criminalsの配列に、$peopleの要素のいずれかが入っているかを上記で判断できる。
@auth
 
    // The user is authenticated...
array_intersectは1個目の配列要素の内、2個目の存在要素を返す (交差)。交差があれば、1個はあるという意味で、any/someになる。
  @endauth
 
 
全部の包含判定したい場合、array_diff ([https://www.php.net/manual/ja/function.array-diff.php PHP: array_diff - Manual]) でできる。
@guest
  $containsAllValues = !array_diff($search_this, $all);
    // The user is not authenticated...
array_diffはarray_intersectと異なり、1個目の配列要素の内、2個目の不在要素を返す (差分)。なので、空なら全包含となる。非空なら非全包含=some。
@endguest
 
完全一致なら、===でOK。
@auth('admin')
 
    // The user is authenticated...
ポイントとしては、1個目の要素は要素数が少ない配列を指定したほうが速くなる。判定だけで、速度が重要なら、foreachで見つかったらすぐreturnしたほうが速い。
@endauth
 
 
array_intersectが実行結果とboolが同じ向きなので、これを使うとわかりやすいだろう。
@guest('admin')
=====Array Functions=====
    // The user is not authenticated...
======compact======
@endguest
Ref: [https://www.php.net/manual/ja/function.compact.php PHP: compact - Manual].
 
@hasSection('navigation')
変数名とその値から、配列を作る。extractの逆。
    <nowiki><div class="pull-right"></nowiki>
 
        @yield('navigation')
MVCのViewに複数の値を渡す場合などによく使う。
    <nowiki></div></nowiki>
======extract======
 
Ref: [https://www.php.net/manual/en/function.extract.php PHP: extract - Manual].
    <nowiki><div class="clearfix"></div></nowiki>
 
  @endif
配列のキー・バリューを変数として取り込む。
======array_map======
*[https://www.php.net/manual/ja/function.array-map.php PHP: array_map - Manual]
  array_map(?callable $callback, array $array, array ...$arrays): array
JavaScriptのmap相当。非常に重要でよく使う。


特に@isset/@emptyをよく使うかも。
callbackにnullを指定すると、複数の配列のzip (unpack) を行う。


===== Switch Statements =====
ただ、array_mapのコールバックの引数は通常配列の要素が想定されていて、連想配列のキーにはアクセスできない。


====== Loops ======
それをしたかったら、array_reduceを使う。らしい。
PHPの反復構造に近いものがある。重要。
@for ($i = 0; $i < 10; $i++)
<nowiki> </nowiki>  The current value is <nowiki>{{ $i }}</nowiki>
@endfor
<nowiki> </nowiki>
@foreach ($users as $user)
<nowiki> </nowiki>  <nowiki><p>This is user {{ $user->id }}</nowiki><nowiki></p></nowiki>
@endforeach
<nowiki> </nowiki>
@forelse ($users as $user)
<nowiki> </nowiki>  <nowiki><li>{{ $user->name }}</nowiki><nowiki></li></nowiki>
@empty
<nowiki> </nowiki>  <nowiki><p>No users</p></nowiki>
@endforelse
<nowiki> </nowiki>
@while (true)
<nowiki> </nowiki>  <nowiki><p>I'm looping forever.</p></nowiki>
@endwhile
公式マニュアルに記載がないが、foreachはPHPのforeachと同じく foreach($array as $key => $value) 形式にも対応している。


====== The Loop Variable ======
[https://www.danielauener.com/howto-use-array_map-on-associative-arrays-to-change-values-and-keys/ Howto use array_map on associative arrays to change values and keys - Daniel Auener]
======array_flip======
[https://www.php.net/manual/ja/function.array-flip.php PHP: array_flip - Manual]


====== Additional Attributes ======
配列のキーと値を反転した配列を返す。元のarrayの値は有効なキーを必要とする。つまり、intかstring。型が違う場合、警告が出て無視される。
[https://laravel.com/docs/9.x/blade Blade Templates - Laravel 9.x - The PHP Framework For Web Artisans]


Laravel v9から使用可能。
また、同じ値が複数ある場合、最後のみが有効になる。


@disabled
====Type declarations/型宣言====
[https://www.php.net/manual/ja/language.types.declarations.php PHP: 型宣言 - Manual]


====== Comments ======
関数の引数、戻り値、クラスのプロパティー (PHP 7.4.0以上) に型を宣言できる。これにより、型を保証でき、その型でなければ、TypeErrorをスローする。
[https://stackoverflow.com/questions/27830200/laravel-blade-comments-blade-rendering-causing-page-to-crash php - Laravel - Blade comments , blade rendering causing page to crash - Stack Overflow]


Bladeテンプレートファイル内でのコメントには注意が必要。
関数の戻り値だけ、型の指定箇所がやや特殊で、それ以外は原則変数の直前。関数の戻り値の場合、(): の後に指定する。
  <nowiki>{{-- code --}}</nowiki> これが基本
  function name(): type {}
PHPコード扱いでのコメントアウトも便利。
@php
/* */
@endphp
<?php /* */ ?>
Bladeの<nowiki>{{-- code --}}</nowiki>は内部の{{}}が変数展開として解釈される。内部に波括弧がある場合は、phpコード扱いでコメントアウトしたほうが安全。


====== PHP ======
  <?php
Bladeテンプレート内でPHPのコードをそのまま記述する際には、専用の指令を使う。
  function sum($a, $b): float {
  @php
    return $a + $b;
  @endphp
これを使うと、「[https://funbrew.tech/2022/06/22/1699/ Laravelのbladeで変数を定義する – FUNBREW]」にあるように、変数を定義してBlade内で使うことができる。変数の定義や空チェックなどを最初にできるので、シンプルになる。
@php
$newValue = $somethingValue;
if (empty($newValue)) {
<nowiki> </nowiki>  $newValue = 'Not Defined';
  }
  }
@endphp
   
   
  <nowiki><div>{{ $newValue }}</nowiki><nowiki></div></nowiki>
  // float が返される点に注意
var_dump(sum(1, 2));
?>
nullable な型とシンタックスシュガー
 
nullableの場合、型名の前に?を指定する (PHP 7.1.0以上)。?TとT|nullは同じ意味。


===== Forms =====
単一の基本型を宣言した場合、 型の名前の前にクエスチョンマーク (?) を付けることで、nullable であるという印を付けることができます。 よって、?T と T|null は同じ意味です。


====== CSRF Field ======
注意: この文法は、PHP 7.1.0 以降でサポートされており、 PHP 8.0で一般化された union 型がサポートされる前から存在します。
アプリ内でHTMLフォームを使用する場合、CSRFトークンフィールドを記述する。


具体的には、form要素の冒頭に@csrfを指定する。
PHP 7.4未満などの場合は、しかたないのでアノテーションで対応する。
<form method="POST" action="/profile">
====Type juggling====
    @csrf
[https://www.php.net/manual/ja/language.types.type-juggling.php PHP: 型の相互変換 - Manual]
 
    ...
</form>


===== @stack/@push/@prepend: Stacks =====
型の相互変換。非常に重要。


* [https://qiita.com/ttn_tt/items/9a3256101f50894827a2 bladeのcomponent化による再利用 #PHP - Qiita]
いろいろ方法がある。
* [https://dad-union.com/php/3136 最適化されたWebページデザイン: Laravel Bladeで個別ページのJavaScriptとCSSファイルを効果的に追記・管理する詳細ガイド|DAD UNION - エンジニア同盟]


名前付きのスタックに、他の場所で使用するビューやレイアウトを格納して、流用できる。
共通なのはキャスト (cast)。<syntaxhighlight lang="php">
<?php
$foo = 10;  // $foo は整数です
$bar = (bool) $foo;  // $bar は boolean です
$fst = "$foo"; // to string.
?>
</syntaxhighlight>C言語と同じで (型) を前置する。ただし、少々長い。


まず@pushする。使いたいか所で@stackすると取り出せる。
文字列への変換は二重引用符囲などもOK。まあ、キャストだけ覚えておくのがシンプル。
====Other====
=====型判定=====
[https://www.php.net/manual/ja/function.gettype.php PHP: gettype - Manual]


使い方としては、componentで@pushでscript要素やstyle要素を記述しておいて、レイアウトで@stackで呼び出す感じ。
PHPでの型確認・判定方法がいくつかある。
@push('scripts')
*gettype: 変数の型を文字列で返す。boolean/integer/double/string/array/object/resouce/resource (closed) (PHP v7.2.0以上)/NULL/ unknown type
    <script src="/example.js"></script>
*get_class: オブジェクトのクラス名
@endpush
*get_debug_type: 変数の型名をデバッグしやす形で取得。
*is_型名: is_array/is_bool/is_callable/is_float/is_int/is_null/is_numeric/is_object/is_resoure/is_scalar/is_string/function_exists/method_exists
基本はis_型名だろう。
===Variables===
====Basics====
Ref: [https://www.php.net/manual/en/language.variables.basics.php PHP: Basics - Manual].
=====Undefined variable=====


<nowiki><head>
* [https://www.php.net/manual/ja/language.types.array.php PHP: 配列 - Manual]
    @stack('scripts')
</head></nowiki>
順番が大事な場合、@prependでpushより先に詰め込める。


扱いは、sectionに似ている。componentのためのsectionのような感じだと思う。
未定義変数 (undefined variable) の値はNULL。


pushしたものは描画のたびにstackで表示される。後のバージョンで@onceというのが登場したので、これを使えば1個だけになる。それまでは自作が必要。
未定義変数 (配列の不在キー) にアクセスすると、E_WARNING (PHP 8未満はE_NOTICE) レベルのエラーが生じて、nullを返す。回避したければ、isset()で検知する。要素の追加時のアクセスは問題ない。


push/stackを使わない場合、そのページに必要なくても、使う可能性のあるcss/jsを親で全部読み込んでおかないといけない。それを回避できる。
未定義変数の検知・制御方法がいくつかある。
*isset ([https://www.php.net/manual/en/function.isset.php PHP: isset - Manual])
*empty ([https://www.php.net/manual/en/function.empty.php PHP: empty - Manual])
* ??: Null 合体演算子/Null collapsing operator
* ??=: NULL合体代入演算子 PHP v7.4以上。
*@: エラー制御演算子
*array_key_exists
issetとempty、Null合体演算子あたりをメインで使う。特にempty。


コンポーネントやテンプレート固有で必要なものを、同じファイルに記載しておいて、反映だけstackでまとめてできる。これが利点だろう。
emptyは以下相当を実施してくれる。値そのものの評価もするので、値が0で正常なときなど場合によっては困る場合もある。
!(isset($var) && $var)
!isset($var) || $var == false
isset($var) && $varは頻繁に使うことになるだろうから、emptyで短縮できる。
empty($var) ? false : true;
$var ?? false;
emptyとissetは関係が逆に似ているがissetは挙動が違う。


===== Other =====
「Returns true if var exists and has any value other than null. false otherwise.」なので、変数の値を評価はしない。nullかどうかだけしかみない。emptyとは扱いが違うので注意する。


====== Bladeのレンダー結果の取得 ======
だから、頻繁に使うだろう。emptyとNull合体演算子の上記の記法はいろんなところで頻繁に使うと思われる。基本重要構文。
場合によっては、componentにBladeの描画結果を埋め込みたいことがある。


* [https://stackoverflow.com/questions/50938285/how-to-get-blade-template-view-as-a-raw-html-string php - How to get Blade template view as a raw HTML string? - Stack Overflow]
ただし、emptyは配列が空の場合もtrueになるので、そこは注意する。配列変数の有無を見たければ、issetを使うしかない。
* [https://laravel.com/api/6.x/Illuminate/View/View.html Illuminate\View\View | Laravel API]
* [https://github.com/laravel/framework/blob/5.6/src/Illuminate/View/View.php#L87 framework/src/Illuminate/View/View.php at 5.6 · laravel/framework]


view関数でViewインスタンス作成後に、render/renderContents/getContentsを呼ぶと描画後のHTML文字列を取得できる。
Null合体演算子はNULLしかカバーしないから、emptyが必要な場面がけっこうある。


これを使う。{!! $var !!} のような感じ。必要に応じてe()でエスケープする。renderを実行しなくても、Viewインスタンスをそのまま渡すとHTMLになっている。けど、エラーが出たときよくわからないのでrender()しておいたほうがいい。
emptyの反対は、strlen/countあたり。ただし、未定義変数のチェックをしてくれないので、!emptyしたほうがいい。
====Variable scope====


=== Database ===
===== About =====
出典: [https://www.php.net/manual/en/language.variables.scope.php PHP: Variable scope - Manual]。


==== Getting Started ====
関数の外で使用するとグローバルスコープになる。ただし、関数内では暗黙にはグローバル変数は使えない。未定義変数扱いになる。
[https://laravel.com/docs/5.8/database Database: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]


クエリービルダー
関数内でグローバル変数を参照したければ、関数内でglobalで明示的に使用したいグローバル変数を宣言する必要がある。
  use Illuminate\Support\Facades\DB;
  <?php
上記のクラスメソッドで生SQLを使えeる。
$a = 1;
$b = 2;
function Sum()
{
    global $a, $b;
    $b = $a + $b;
}
あるいは、$GLOBALS配列にグローバル変数が入っているのでこれを使う。


===== Running Raw SQL Queries =====
なお、'''波括弧のブロックスコープは存在しない'''。C系言語の感覚だと、波括弧でスコープが作られそうなイメージがあるが、PHPの波括弧はスコープを作らない。あくまで、関数の内部かどうか。
[https://qiita.com/alpha_z/items/3d2e3c283b4cd8dbc955 LaravelのクエリビルダでSQL文を直接実行(select,insert,update,delete,その他) #Laravel - Qiita]


DB::select/insert/update/deleteなどよく使うCRUD操作はメソッドがある。そういうのとは関係なしに、SQLを直実行する場合DB::statementを使う。
逆にいうと、関数内に定義される関数・クラスも基本グローバル。
DB::statement('drop table users');
なお、DB::statementは1文ずつしか実行できない。複数実行する場合、1文ごとにDB::statementを記載する。


==== Transaction ====
子関数に変数を渡したい場合、引数かグローバル変数しかない。他に隠蔽したり、親関数からスコープを引き継ぎたい場合、無名関数を使うしか無い。


* [https://laravel.com/docs/5.8/database Database: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]
===== Super global =====
* [https://qiita.com/shimizuyuta/items/dc69fb30d9f8600592db 新卒がLaravelのトランザクション実装について学んだこと #初心者 - Qiita]
[https://www.php.net/manual/ja/language.variables.superglobals.php PHP: スーパーグローバル - Manual]
* [https://www.happylifecreators.com/blog/20220513/ Laravelのトランザクションとエラーハンドリグについて | Happy Life Creators]
* [https://www.yui-web-beginner.net/laravel-transaction/ Laravelのトランザクション処理を2パターン徹底解説 | 現役プログラマーYuiの開発ブログ]
* [https://shinyasunamachi.com/blog/Laravel%E3%81%A7%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E5%87%A6%E7%90%86%E3%81%AE%E3%82%A8%E3%83%A9%E3%83%BC%E3%83%8F%E3%83%B3%E3%83%89%E3%83%AA%E3%83%B3%E3%82%B0%E3%82%92%E8%A1%8C%E3%81%86%E6%96%B9%E6%B3%95 Laravelでトランザクション処理のエラーハンドリングを行う方法 | Shinya Sunamachi]


DB::transactionに処理の無名関数を指定してやると、DB処理が失敗したら自動的にロールバックする。
全てのスコープで使用可能な組込変数。関数、メソッド内でもglobal $variable;とする必要がない。
try {
$result = DB::transaction(function () use ($a, $b) {
    DB::table('users')->update(['votes' => 1]);
 
    DB::table('posts')->delete();
    return true;
});
} catch (Exception $e) {
    Log::error($e->getMessage());
    return;
}


2引数でデッドロック用のリトライ回数を指定できる。基本はいらない?
* $GLOBALS: グローバル変数の連想配列。
* $_SERVER
* $_GET
* $_POST
* $_FILES
* $_COOKIE
* $_SESSION
* $_REQUEST
* $_ENV


無名関数内でreturnすると、返せる。DB::transactionで失敗時の処理を個別にしたい場合、内部でthrowしてtry-catchをする。
====Variables From External Sources====
Ref:
*[https://www.php.net/manual/en/language.variables.external.php PHP: Variables From External Sources - Manual]
*[https://www.php.net/manual/en/faq.html.php PHP: PHP and HTML - Manual]
*[https://www.php.net/manual/en/reserved.variables.php PHP: Predefined Variables - Manual]
*[https://www.php.net/manual/en/book.filter.php PHP: Filter - Manual]
PHPとHTMLフォームの関係がある。重要。


==== Migrations ====
配列渡しはPHP側の仕様。
[https://laravel.com/docs/5.8/migrations Database: Migrations - Laravel 5.8 - The PHP Framework For Web Artisans]


マイグレーションはデータベースのバージョン管理のようなもの。データベーススキーマを簡単に修正・共有を可能にする。
=== Constants ===


===== Generating Migrations =====
==== About ====
make:migrationコマンドでマイグレーションファイルを作れる。
php artisan make:migration create_users_table
CRUD_テーブル名_tableなどのような命名規則にしておくとわかりやすい。


実行すると、database/migrationsディレクトリーにマイグレーションファイルが生成される。
* [https://www.php.net/manual/ja/language.constants.php PHP: 定数 - Manual]
* [https://www.php.net/manual/ja/function.define.php PHP: define - Manual]


オプションで--create=テーブル名、--table=テーブル名がある。
定数は値のためのID (名前)。基本的にスクリプト実行中に変更できない。大文字小文字を区別するが、慣習として大文字で表記する。


現在のテーブルの状況などで、マイグレーションファイルのひな形が変わる。ある程度マイグレーション名から推測してくれるが、日本語などが入るときかなくなる。オプションを指定したほうが無難。
constキーワードか、define関数で定義できる。constの場合、制約がある。


===== Migration Structure =====
constで指定可能なのは、スカラー式 (bool/int/float/string) と、スカラー式のみのarray。動的な設定はできない。
コマンド実行で生成されたテンプレートには、upとdownメソッドが用意されている。ここにテーブルの更新と、巻戻の処理を記載する。


===== Running Migrations =====
変数と異なり、$の前置は不要。
以下のコマンドで用意済みのマイグレーションを実行する。
php artisan migrate


===== Command =====
定数の定義判定は、defined()を使う。


* migrate:rollback: マイグレーションをロールバックする。デフォルトだと最後の1回分。--step=Nで回数を指定できる。
定数の変数との違いは以下。
* migrate:reset: 全部ロールバックする。
* migrate:refresh: rollback+migrate。
* migrate:fresh: ロールバックではなく、テーブルを削除してから戻す。


===== 既存DBの途中からの管理 =====
* $不要。
* スコープに関係なく、あらゆる場所からアクセス可能。
* 後から再定義、未定義不能。
* スカラー値と配列のみ。


* [https://gamushiros.hatenablog.com/entry/2019/04/23/000000 Laravelのmigrateを途中から実行する - mk-toolブログ]
constはコンパイル時に定義されるため、トップレベル以外、つまりブロック内部 (関数/ループ/if/try) で宣言できない。defineはできる。
* [https://teratail.com/questions/362984 Laravel 既存DBをマイグレーションする方法]
* [https://www.reddit.com/r/laravel/comments/aarkm7/laravel_migration_how_to_autoskip_tables_that/ Laravel Migration: how to auto-skip tables that already exist? : r/laravel]


今後の変更分だけ管理するということもできる。
==== define/const ====


なお、中途半端にLaravelのマイグレーションファイルがある場合、migrationsテーブルにデータを手動で登録しておくと、マイグレーション済みと認識できる。
* [https://qiita.com/schrosis/items/485b984e05b2eb4521b4 PHPの「define」と「const」の違い #定数 - Qiita]
* [https://qiita.com/nishimura/items/a396c999a85fa4cbc4a0 PHPでプログラム全体の設定に使う変数の保持の仕方 #PHP - Qiita]


作ってあげるデータは、id,migration, batchを持つデータで、idは1からインクリメント、migrationはマイグレーションのファイル名(.phpは除く)、batchを1(たぶんこれは何度マイグレーションを実行したかを保っておくもので状態を1つ戻すときに使われるのでは?)に設定すればよい。
{| class="wikitable"
{| class="wikitable"
!id
|+
!migration
!項目
!batch
!const
!define
|-
|-
|1
|構文
|2017_09_01_134421_create_prefectures_table
|予約語 (少し速い)
|1
|関数
|-
|-
|2
|戻り値
|2017_09_01_134422_create_cities_table
|なし
|1
|あり
|-
|定義元
|スカラー値のみ
|変数/関数OK
|-
|クラス定数
|x
| -
|-
|使用箇所
|制御ブロック内部以外
|どこでも
|-
|スコープ
|名前空間
|グローバル
|}
|}
SQLだと以下相当。
defineはブロック内で使えるので、何らかの条件で定義を変更できるのが利点。例えば、環境を本番とデバッグに変えたりなど。
insert into migrations(migration, batch) values('2015_12_08_134409_create_tables_script',1);
 
insert into migrations(migration, batch) values('2015_12_08_134410_create_foreign',1);
動的に変更したいならdefine、それ以外は名前空間やクラス定数として使えるconstだろうか。関数内のマジックナンバー的な使い方はできない。そういうのは、普通の変数で取り扱う。
insert into migrations(migration, batch) values('2015_12_08_134411_create_index',1);
 
ただ、constはアプリの設定として使うことはない。クラスの固有値の定義。
 
==== constant ====
[https://www.php.net/manual/ja/function.constant.php PHP: constant - Manual]
 
定数名の文字列で、定数の値を取得したい場合に使える。
 
定数の他に、enumのcaseにも使える。
 
==== class ====
クラス内に定数を定義できる。デフォルトでpublic。staic変数的な扱い。インスタンスではなく、クラスが保有する。


===== SQLでのマイグレーション =====
==== predefined ====
[https://stackoverflow.com/questions/46507655/laravel-migration-is-it-possible-to-use-sql-instead-of-schema-commands-to-crea database - Laravel migration - is it possible to use SQL instead of schema commands to create tables and fields etc? - Stack Overflow]
 
* [https://www.php.net/manual/ja/language.constants.predefined.php PHP: 自動的に定義される定数 - Manual]
* [https://www.php.net/manual/ja/reserved.constants.php PHP: 定義済みの定数 - Manual]
 
言語で定義済みの定数がいろいろある。true/false/nullなど。
 
==== Magic/マジック定数 ====
使用箇所で値が変化する定数 (マジック定数) が9個ある。C言語のマクロに近い。コンパイル時に解決される。大文字小文字を区別しない。
{| class="wikitable"
|+PHP の "マジック" 定数
!名前
!説明
|-
|<code>[[/www.php.net/manual/ja/language.constants.magic.php#constant.line|__LINE__]]</code>
|ファイル上の現在の行番号。
|-
|<code>[[/www.php.net/manual/ja/language.constants.magic.php#constant.file|__FILE__]]</code>
|ファイルのフルパスとファイル名 (シンボリックリンクを解決した後のもの)。 インクルードされるファイルの中で使用された場合、インクルードされるファイルの名前が返されます。
|-
|<code>[[/www.php.net/manual/ja/language.constants.magic.php#constant.dir|__DIR__]]</code>
|そのファイルの存在するディレクトリ。include の中で使用すると、 インクルードされるファイルの存在するディレクトリを返します。 つまり、これは <code>dirname(__FILE__)</code> と同じ意味です。 ルートディレクトリである場合を除き、ディレクトリ名の末尾にスラッシュはつきません。
|-
|<code>[[/www.php.net/manual/ja/language.constants.magic.php#constant.function|__FUNCTION__]]</code>
|関数名。無名関数の場合は、<code>{closure}</code>
|-
|<code>[[/www.php.net/manual/ja/language.constants.magic.php#constant.class|__CLASS__]]</code>
|クラス名。 クラス名には、そのクラスが宣言されている名前空間も含みます (例 <code>Foo\Bar</code>)。 トレイトのメソッド内で __CLASS__ を使うと、 そのトレイトを use しているクラスの名前を返します。
|-
|<code>[[/www.php.net/manual/ja/language.constants.magic.php#constant.trait|__TRAIT__]]</code>
|トレイト名。 トレイト名には、宣言された名前空間も含みます (例 <code>Foo\Bar</code>)。
|-
|<code>[[/www.php.net/manual/ja/language.constants.magic.php#constant.method|__METHOD__]]</code>
|クラスのメソッド名。
|-
|<code>[[/www.php.net/manual/ja/language.constants.magic.php#constant.namespace|__NAMESPACE__]]</code>
|現在の名前空間の名前。
|-
|<code>ClassName::class</code>
|完全に修飾されたクラス名。
|}
どれもよく使う。


upメソッドにDB::updateなどで生のSQLをそのまま書けばOK。
===Operators===
class AddColumnsToUsersTable extends Migration
[https://www.php.net/manual/en/language.operators.php PHP: Operators - Manual]
{
====Assignment/代入演算子====
    /**
*[https://www.php.net/manual/en/language.operators.assignment.php PHP: Assignment - Manual]
      * Run the migrations.
*[https://qiita.com/rana_kualu/items/e15a9b7c12f175380244 【PHP7.4】PHPの新たな演算子??=ってなんぞ? #NULL合体代入演算子 - Qiita]
      *
??=
      * @return void
    // NULL合体代入演算子
      */
     $id ??= getId();
     public function up()
     {
     // これと同じ
        Schema::table('users', function (Blueprint $table) {
    $id = $id ?? getId();
            $table->string('name');
    $id = @$id ?: getId();
            $table->string('age');
    $id = isset($id) ? $id : getId();
            $table->timestamps();
NULL合体演算子の代入版。nullの場合の代入が簡単になった。PHP 7.4から使用可能。
        });
====Comparison/比較演算子====
*[https://www.php.net/manual/ja/language.operators.comparison.php PHP: 比較演算子 - Manual]
*[https://www.php.net/manual/ja/migration70.new-features.php PHP: 新機能 - Manual]
=====三項演算子 (条件演算子)=====
if/elseの短縮表記。デフォルト値の設定などでよく使う。
<?php
// 三項演算子の使用例
$action = (empty($_POST['action'])) ? 'default' : $_POST['action'];
   
   
        DB::update('update users set age = 30 where name = ?', ['John']);
// 上記は以下の if/else 式と同じです。
    }
if (empty($_POST['action'])) {
  $action = 'default';
} else {
  $action = $_POST['action'];
  }
  }
downメソッドにはDropIfExistsで削除すればOK。
?>
PHP特有事項として、真ん中を省略できる。その場合、1個目がtrueならそれがそのまま戻る。JavaScriptとかC系言語でも真ん中は省略できない。


==== Seeding ====
式 <code>expr1 ?: expr3</code> の結果は、expr1 が <code>[https://www.php.net/manual/ja/reserved.constants.php#constant.true true]</code> と同等の場合は expr1、 それ以外の場合は expr3 となります。 この場合、expr1 は一度だけ評価されます。
[https://laravel.com/docs/5.8/seeding Database: Seeding - Laravel 5.8 - The PHP Framework For Web Artisans]


マイグレーションで用意したDBのデフォルトデータの用意の話。
条件演算子のネストはわかりにくいので推奨されない。が、条件演算子の省略形は安定している。false以外の最初の引数を評価する。
<?php
echo 0 ?: 1 ?: 2 ?: 3, PHP_EOL; //1
echo 0 ?: 0 ?: 2 ?: 3, PHP_EOL; //2
echo 0 ?: 0 ?: 0 ?: 3, PHP_EOL; //3
echo $undefinedVariable ?? false ?: 'false default';
?>
NULL合体演算子はnullの時のデフォルト値になるが、こちらはfalseの場合のデフォルト値設定。意味が違う。未定義変数アクセスをガードできないが、それ以外であれば条件演算子の短縮表記のほうをよく使う。非常に重要。


デフォルトデータをシードと呼んでおり、シードを用意する機能をシーディングと呼んでいる。
Null合体演算子を組み合わせて、未定義のなどの場合のデフォルト値設定で役立つ。


シードを作成するためのスクリプト (シーダー)  ファイルを生成して、記述して実行する流れ。
[https://www.php.net/manual/ja/language.operators.logical.php PHP: 論理演算子 - Manual]


===== Writing Seeders =====
elvis演算子と呼ばれることもある模様。
以下のコマンドでシーダーを作成する。
php artisan make:seeder UsersTableSeeder
database/seedsにファイルが作成される。


中身はrunメソッドがあるだけ。run内でinsertするだけ。
It also combines nicely with the ?? operator, which is equivalent to an empty() check (both isset() and `!= false`):
$x->y ?? null ?: 'fallback';
instead of:
empty($x->y) ? $x->y : 'fallback'
=====Null 合体演算子/Null collapsing operator=====
<?php
// $_GET['user'] を取得します。もし存在しない場合は
// 'nobody' を用います。
$username = $_GET['user'] ?? 'nobody';
// 上のコードは、次のコードと同じ意味です。
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
$var ?? 'value = isset($var) ? $var : 'value';


===== Running Seeders =====
変数がnullの場合のガードの簡易記法。PHP v7.0.0で追加。非常に便利。
追加したシーダーを認識させるために以下のコマンドでautoloadを更新する。
====Error Control/エラー制御演算子@====
composer dump-autoload
*[https://www.php.net/manual/ja/language.operators.errorcontrol.php PHP: エラー制御演算子 - Manual]
シーダーを用意したらコマンドを実行して作成する。
*[https://toku1.jp/programming-php-error-handle-operator/ 【PHP】エラー制御演算子に正しい使い道はあるのか? | とりあえず、いっとく!]
php artisan db:seed
式の直前に@を前置すると、その式のエラーメッセージを無視する。
--classでシーダー名を指定もできる。あとからシーダーを追加する場合は--classで指定するイメージ。


==== Error ====
基本的に使わないほうがいい。if文などでガードするより処理が遅い。ただし、Viewなどであまり影響ない場合などは記述がシンプルになるという利点もあるかも。


===== Using Docker I get the error: "SQLSTATE[HY000] [2002] No such file or directory" =====
ただ、やっぱり基本は使わないほうがいい。バグの見落としになる。
[https://stackoverflow.com/questions/40075065/using-docker-i-get-the-error-sqlstatehy000-2002-no-such-file-or-directory php - Using Docker I get the error: "SQLSTATE[HY000] [2002] No such file or directory" - Stack Overflow]
====Logical/論理演算子====
[https://www.php.net/manual/ja/language.operators.logical.php PHP: 論理演算子 - Manual]


[https://qiita.com/b-coffin/items/8103583efe3767b6748e PHPのPDOをDockerコンテナ内で使おうとしたところ、"No such file or directory" エラーが発生した話 #docker-compose - Qiita]
論理積と論理和が、and/orと&&/||で2種類存在する。演算子の優先順位が違う。
// $g に代入されるのは、(true && false) の評価結果です
// これは、次の式と同様です: ($g = (true && false))
$g = true && false;
// $h に true を代入してから "and" 演算子を評価します
// これは、次の式と同様です: (($h = true) and false)
$h = true and false;
なお、PHPの論理演算子は、常に論理値 (true/false) を返すので注意する。
$a = $var || 'default';
上記のように、デフォルト値の代入扱いでor演算子を使うことはできない。同じ論理型同士なら成立はするが。


dockerで.envのDB_HOSTの指定間違い。dockerのservice名をホスト名に指定する必要がある。
デフォルト値扱いにしたければ、短縮条件演算子?:や、ヌル合体演算子??を使う。


===== local.ERROR: could not find driver =====
==== ...演算子/スプレッド演算子 ====
[2024-07-02 15:35:42] local.ERROR: could not find driver (SQL: select * from `sessions` where `id` = 0cDH7URcrsFzC7hPYlbAQcezNVjdDM16OJh1aCSZ limit 1) {"exception":"[object] (Illuminate\\Database\\QueryException(code: 0): could not find driver (SQL: select * from `sessions` where `id` = 0cDH7URcrsFzC7hPYlbAQcezNVjdDM16OJh1aCSZ limit 1) at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 0): could not find driver at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18, PDOException(code: 0): could not find driver at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:40)
このエラーはphpのモジュール不足。LaravelはPDOを使う。mysqliではなく。
RUN docker-php-ext-install pdo_mysql
これが必要。


=== Eloquent ===
* 関数
** 可変長引数リストと関数呼出 [https://www.php.net/manual/ja/functions.arguments.php#functions.variable-arg-list PHP: 関数の引数 - Manual]。PHP 5.6で導入 ([https://www.php.net/manual/ja/migration56.new-features.php PHP: 新機能 - Manual])。名前付き引数に対応したPHP 8.0から連想配列も対応。PHP 8.1から可変長引数と名前付き引数の同時使用 ([https://www.php.net/manual/ja/migration81.new-features.php#migration81.new-features.core.named-arg-after-unpack PHP: 新機能 - Manual])。アンパックと通常名前付き引数がある場合、後のやつで上書き不能で実行時エラー。
** 第一級callableを生成する記法: PHP 8.1.0で導入 ([https://www.php.net/manual/ja/functions.first_class_callable_syntax.php#functions.first_class_callable_syntax PHP: 第一級callableを生成する記法 - Manual])。Closure::fromCallableの無名関数作成時の別の方法。
* 配列のアンパック: [https://www.php.net/manual/ja/language.types.array.php#language.types.array.unpacking PHP: 配列 - Manual]。array_mergeの代替記法。
** 数値キー・単純配列はPHP 7.4で導入 ([https://www.php.net/manual/ja/migration74.new-features.php#migration74.new-features.core.unpack-inside-array PHP: 新機能 - Manual])。
** 文字キー・連想配列はPHP 8.1 ([https://www.php.net/manual/ja/migration81.new-features.php#migration81.new-features.core.unpacking-string-keys PHP: 新機能 - Manual]、[https://qiita.com/shigakin/items/5e4a2784bbd6b227f4d3 【PHP8.1】あなたはどっち? array_merge VS unpacking(スプレッド演算子) #PHP - Qiita])。
** アンパックと通常名前付き引数がある場合、後のやつで上書き。
** 配列のアンパックは、性能面で問題があり、連想配列がPHP 8.1以上というのがあり、array_mergeを使ったほうがよさそう。
* [https://qiita.com/mpyw/items/835050cbb5ad8a4c0710 PHP の ... (3点ドット, Three Dots) の種類,全部言えるかな? #QiitaEngineerFesta2022 - Qiita]


==== Getting Started ====
関数と配列の2か所で意味がある。配列の他、Traversableオブジェクトも可能。
[https://laravel.com/docs/5.8/eloquent Eloquent: Getting Started - Laravel 5.8 - The PHP Framework For Web Artisans]


Eloquent ORM。
配列や配列変数の直前に...を前置する (スペースは任意)。


===== Defining Models =====
関数の場合、関数定義時の仮引数と、関数呼出時に使用可能。
make:modelコマンドでモデルインスタンスを作成できる。
php artisan make:model Flight
-mで新規作成のマイグレーションファイルも一緒に作れる。


====== Eloquent Model Conventions ======
関数定義時の仮引数で指定すると、その変数が可変長引数を受け入れることを意味する。型宣言はその左に指定可能。これにより、func_get_args()を使わなくてもよくなった。


====== Table Names ======
関数呼出時に使用すると、引数を展開してくれる。配列のアンパックに近い。
[https://qiita.com/igz0/items/d14fdff610dccadb169e Laravelで「Base table or view not found: 1146 Table」エラーが出るときの対処法 #PHP - Qiita]
<?php
 
function sum(...$numbers) {
Eloquentのモデルでは、デフォルトでクラス名を複数形のスネークケースにしたものをテーブル名とみなす。
    $acc = 0;
  SQLSTATE[42S02]: Base table or view not found: 1146 Table 'your_db.your_models' doesn't exist (SQL: select * from `your_models`)
    foreach ($numbers as $n) {
不在の場合、上記のエラーが出る。
        $acc += $n;
    }
    return $acc;
}
  echo sum(1, 2, 3, 4);
?>


デフォルト以外のテーブル名を使いたければ、$tableにテーブル名を指定する。
  <?php
  <?php
 
  function add($a, $b) {
namespace App;
     return $a + $b;
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
  {
     /**
      * The table associated with the model.
      *
      * @var string
      */
    protected $table = 'my_flights';
  }
  }
プロジェクトでテーブル名に日本語を使う場合など、クラスメイとテーブル名が同一なら、以下のような親クラスを使って、これを継承すると同じ処理を記載しなくていい。
<?php
   
   
  namespace App\Models;
  echo add(...[1, 2])."\n";
   
   
  use Illuminate\Database\Eloquent\Model;
  $a = [1, 2];
   
  echo add(...$a);
  class BaseModel extends Model
  ?>
  {
 
    function __construct()
  <?php
     {
function total_intervals($unit, DateInterval ...$intervals) {
         // デフォルトだと複数形のスネークケースのテーブルを探すため、クラス名=テーブル名で上書き。
     $time = 0;
        $this->table = (new \ReflectionClass($this))->getShortName();
    foreach ($intervals as $interval) {
         $time += $interval->$unit;
     }
     }
    return $time;
  }
  }
[https://qiita.com/shigakin/items/5e4a2784bbd6b227f4d3 【PHP8.1】あなたはどっち? array_merge VS unpacking(スプレッド演算子) #PHP - Qiita]
なお、配列のアンパックに関しては、array_mergeのほうが速くてメモリーも少ないとのこと。
===Control Structures===
Source: [https://www.php.net/manual/en/language.control-structures.php PHP: Control Structures - Manual].
====制御構造に関する別の構文====
[https://www.php.net/manual/ja/control-structures.alternative-syntax.php PHP: 制御構造に関する別の構文 - Manual]
if、 while、for、 foreach、switch に関する別の構文がある。開き波括弧部分を:に、閉じ波括弧部分をendif;,endwhile;, endfor;,endforeach;, endswitch;などにできる。else:とelseif:に注意。
この構文は存在だけ知っておくだけでいいと思われる。
====elseif/else if====
[https://www.php.net/manual/ja/control-structures.elseif.php PHP: elseif/else if - Manual]
1単語で書ける。結果は同じだが、文法的な意味が異なる。
====for/foreach====
foreachは配列の反復処理のための制御構造。
foreach(iterable_expression as $value)
foreach(iterable_expression as $key => $value)
$keyも使いたい場合、2番目の形式を使う。
ループ中に$valueの要素を直接変更したい場合、&をつけておく。
foreach(iterable_expression as &$value)
====declare====
Source: [https://www.php.net/manual/en/control-structures.declare.php PHP: declare - Manual].
PHPUnitのサンプルコード ([https://phpunit.de/getting-started/phpunit-9.html Getting Started with Version 9 of PHPUnit – The PHP Testing Framework]) などで冒頭に以下の記述がある。
<?php declare(strict_types=1);
これの意味が分かっていなかったので整理する。
declare文 (construct) は、コードブロックの実行指令となる。以下の構文となる。
declare (<directive>)
  <statement>
<directive> はdeclareブロックの挙動を指示する。指定可能なものは以下3個だ。
#ticks
#encoding
#strict_types: =1の指定でPHPの暗黙の型変換を無効にする (ストリクトモード)。ただし、影響するのはスカラー型のみ。型が違う場合、TypeErrorの例外が発生する。
指令はファイルコンパイル時に処理されるので、リテラル値のみが使用可能で、変数や定数は使用不能。
declareブロックの <statement> は、<directive> の影響を受ける実行部だ。
declare文はグローバルスコープで使われる。登場以後のコードに影響する。ただし、他のファイルからincludeされても、親ファイルには影響しない。だから安心して使える。
型安全にするために、基本的にPHPファイルの冒頭に<code>declare(strict_types=1);</code>を書いておいたほうがよいだろう。
==== return ====
[https://www.php.net/manual/ja/function.return.php PHP: return - Manual]
関数を終了させて、結果を呼び出し元に返すというのは他の言語同様の動きだが、いくつか注意すべき挙動・使用方法がある。
returnで引数を省略すると、戻り値はnullになる。
呼び出し方法、場所で挙動が変わる。
* 関数/eval内: 即座に関数を終了し、引数を関数の値として返却。
* グローバルスコープ: スクリプト自体を終了。
* include/require内: 呼び出し元のファイルに制御を戻す。includeの場合、引数はincludeの戻り値になる。
return文は関数ではないので、引数の括弧は不要。紛らわしいのでないほうがいい。
include内で使えるというのがみそ。config.phpでreturnだけした設定一覧を記述しておいて、includeで変数に取り込むというのをよくやる。
==== require/include/require_once/include_once ====
===== Basic =====
includeは指定したファイルを読み込み評価する。絶対パスで指定しない場合、include_pathの設定を利用する。include_pathにもなければ現在ディレクトリーも探す。
絶対パス、相対パスの前置があると、include_pathは無視する。


====== Timestamps ======
ファイルが読み込まれると、ファイル内のコードは、includeが実行された行の変数スコープを継承する。つまり、呼び出し行で利用可能な全変数がファイル内でも使用可能。ファイル内で定義された関数やクラスはすべて、グローバルスコープになる。ただし、includeが関数定義内に配置されたら、コードは関数内で定義されているとみなす。
[https://qiita.com/ikadatic/items/2237a3c1b837894dfc30 LaravelのEloquentモデルでupdated_atがないテーブルを使う方法 #PHP - Qiita]


[https://stackoverflow.com/questions/28277955/laravel-unknown-column-updated-at php - Laravel Unknown Column 'updated_at' - Stack Overflow]
ファイルの読込時にはHTMLモードになる。そのため、ファイル内でPHPコードを実行するなら、<?php ?>で囲む必要がある。


デフォルトで、Eloquentはcreated_atとupdated_atカラムを期待する。使っていないならば、以下のエラーが出る。
includeに失敗したらFALSEを返し、E_WARNINGを発生させる。成功したら、戻り値は1。ただし、ファイル内でreturnを実行したら、その値を返す。
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18, PDOException(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' at /var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:82)
[stacktrace]


$timestamps=falseにしておく。
includeは特別な言語構造のため、引数に括弧は不要。結果を評価したいならば、全体を括弧で囲む。
  <?php
  // 動作します。
 
  if ((include 'vars.php') == TRUE) {
namespace App;
     echo 'OK';
 
use Illuminate\Database\Eloquent\Model;
 
  class Flight extends Model
{
    /**
      * Indicates if the model should be timestamped.
      *
      * @var bool
      */
     public $timestamps = false;
  }
  }


===== Retrieving Single Models / Aggregates =====
===== require/include =====
モデルからデータを取得するいくつか主要なメソッドがある。
requireはincludeとほぼ同じ。違いは、失敗時にE_COMPILE_ERRORが発生して処理を中断する点。includeはE_WARNINGで処理は継続する。
 
使い分けとして、変数読込などで読み込めなくても処理を進めて問題ない場合に、include。
 
関数定義など、絶対必要なものはrequireなど。
 
===== _once =====
読込済みなら、再読込しない点がinclude/requireとの決定的な違い。関数の複数定義のエラーを回避できたりする。


* find: プライマリーキーから値を探す。
読み込めたらtrueを返す。
* all: 全項目を取得する。
* where:
* findOrFail


メソッドチェーン
===== config.php =====


* first: 先頭項目を取得する。
* [https://stackoverflow.com/questions/14752470/creating-a-config-file-in-php Creating a config file in PHP - Stack Overflow]
* firstOrFail
* [https://blog.websandbag.com/entry/2018/12/04/160218 【PHP】静的なconfigファイルの書き方 - websandbag ブログ]
* count
* max


===== Inserting & Updating Models =====
includeとreturnの組み合わせのconfig.phpの設定ファイルをいろんなアプリで使われている。
<?php
return [
    'name' => 'hoge',
    'value' => 'fuga',
];
?>


====== Insert ======
モデルインスタンスを作成し、属性をセットし、最後にsaveを呼ぶ。
  <?php
  <?php
 
  namespace App\Http\Controllers;
  // configファイルを変数に代入
 
$config = include __DIR__ . '/config.php';
  use App\Flight;
   
  use Illuminate\Http\Request;
  // 呼び出し。
  use App\Http\Controllers\Controller;
  var_dump($config['name']);
 
   
  class FlightController extends Controller
  ?>
  {
こういう形式。このreturnだけの文は、ほぼinclude前提。
    /**
 
      * Create a new flight instance.
編集対象のアプリの設定を、既存コードと分離する際に、いい方法。
      *
 
      * @param  Request  $request
config.phpをアプリ内で作りたい場合、「[https://laracasts.com/discuss/channels/laravel/how-to-create-dynamically-create-configcustomphp-config-file How to create Dynamically create config/custom.php config file]」にあるように、var_exportを使うとよい。
      * @return Response
// create the array as a php text string
      */
$text = "<?php\n\nreturn " . var_export($myarray, true) . ";";
    public function store(Request $request)
 
    {
===== config class =====
        // Validate the request...
config.phpをどう用意するかは議論がある。
 
 
        $flight = new Flight;
* [https://stackoverflow.com/questions/10987703/is-it-right-to-set-a-config-php-class-to-keep-project-settings Is it right to set a config PHP class to keep project settings? - Stack Overflow]
 
* [https://docs.php.earth/security/configuration/ Configuration in PHP applications | PHP.earth]
        $flight->name = $request->name;
* [https://qiita.com/nishimura/items/a396c999a85fa4cbc4a0 PHPでプログラム全体の設定に使う変数の保持の仕方 #PHP - Qiita]
 
* [https://php-archive.net/php/config-class/ [PHP]コンフィグファイルから設定情報を読み込むためのConfigクラス | PHP Archive]
        $flight->save();
* [https://qiita.com/satorunooshie/items/ca41f7c824c7ea747708 PHPでconfigファイルをオートロードで呼び出す方法 #Config - Qiita]
    }
* [https://stackoverflow.com/questions/14659769/using-a-config-file-from-within-a-php-class Using a config file from within a php class - Stack Overflow]
}
* [https://stackoverflow.com/questions/33742740/php-oop-config-class PHP OOP Config Class - Stack Overflow]
 
include/returnではなくて、クラスのconst定数にするという。


====== Mass Assignment ======
* クラスのconst定数
[https://qiita.com/yyy752/items/820260163d4883efb132 Add [] to fillable property to allow mass assignment onの解決方法 #初心者 - Qiita]
* iniファイル/parse_ini_file
Add [_token] to fillable property to allow mass assignment on [App\].
こんなエラーが出る。


プロパティーの代入で1個ずつ設定するほかに、まとめて1行での保存もできる。ただし、一括割り当ては危険なので、保護されており、fillable属性かguarded属性の指定が必要。
他に、configクラスを用意しておいて、シングルトンか、staticメソッドで参照する形。


fillメソッドは連想配列でデータをモデルにセットする。
どれくらいの頻度で参照するか次第。参照頻度が低いなら、getで毎回設定ファイルを読み込む。多いならstaticのクラス変数にもたせる。
$form = $request->all();
===Function===
unset($form['_token']);
$flight->fill(['name' => 'Flight 22']);
フォームの項目と対応させておけばそのままセットできる。


====== fillable ======
==== User defined ====
まず、最初に一括割り当て可能な属性を定義する。
[https://www.php.net/manual/ja/functions.user-defined.php PHP: ユーザー定義関数 - Manual]


fillableプロパティーで、一括割り当て可能なカラムを定義できる。
関数は以下のような構文で定義する。
  <?php
  <?php
 
  function foo($arg_1, $arg_2, /* ..., */ $arg_n)
  namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
  {
  {
     /**
     echo "関数の例\n";
      * The attributes that are mass assignable.
     return $retval;
      *
      * @var array
      */
     protected $fillable = ['name'];
  }
  }
?>
関数内では、他の関数やクラス定義を含む、PHPのあらゆるコードを使用可能。関数内で関数を定義できないC言語とは異なる。


====== Guarding Attributes ======
PHPでは、変数と異なり、関数やクラスは全てグローバルスコープ。関数内で定義した関数も外部から呼び出し可能。スコープが欲しければ、無名関数を使う。
$fillableはホワイトリスト方式。$guardedも使用できる。一括割り当て可能にしたくない属性を指定する。ブラックリスト。
$fillableと$guardedのどちらかが最低限必要。


==== Relationships ====
また、関数のオーバーロードもできない。関数をunsetしたり、再定義も不能。


===== Defining Relationships =====
可変引数と、デフォルト引数もある。


====== One To One ======
==== Argument ====
配列同様に、PHP 8.0.0から、引数リストの最後のカンマが許容される。
 
===== Reference =====
引数はデフォルトで値渡しになる。値がコピーされて渡される。関数内部で引数自体を修正したい場合、リファレンス渡しにする。
 
関数定義で変数の前に&をつけると、リファレンス参照になる。
  <?php
  <?php
 
  function add_some_extra(&$string)
  namespace App;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
  {
  {
     /**
     $string .= 'and something extra.';
      * Get the phone record associated with the user.
      */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
  }
  }
主テーブルに外部キーが埋め込まれていて、シンプルな場合、上記のように外部テーブルをメソッド名にしてhasOneを実行すると全部返せる。非常にシンプル。
$str = 'This is a string, ';
add_some_extra($str);
echo $str;    // 出力は 'This is a string, and something extra.' となります
?>


プロパティーとしてアクセス可能になる。1対1だから、以下のようにプライマリーキーなどで参照できる。
===== Default =====
$phone = User::find(1)->phone;
関数定義時に、引数部分で変数に値を代入するようにして、デフォルト値を定義できる。引数が指定されなかった場合に使われる。なお、nullが渡された場合も、デフォルト値の代入はされないので注意する。
デフォルトだと、外部キーは、 [モデル名_id] であることを想定している。これじゃないなら、以下のように引数で指定しておく。
function makecoffee($type = "cappuccino")
return $this->hasOne('App\Phone', 'foreign_key');
{
 
    return "Making a cup of $type.\n";
===== Eager Loading =====
}
Eloquentのリレーションにプロパティーでのアクセス時、リレーションデータは "lazy loaded" になる。これはプロパティーへのアクセスまで、実際には読み込まないことを意味する。つまり、アクセスのたびに読み込みが発生する。Eager Loadが可能になる。
デフォルト値には、定数を指定できる。PHP 8.1.0から、new ClassName記法でインスタンスも指定できる。


Eagerは、熱心な、せっかちなという形容詞。
デフォルト引数は、デフォルト値のない引数の右側の必要がある。そうでない場合、省略できず、指定する意味がなくなく。


例えば、N件のデータを全部表示させたい場合、以下のようなコードを記述する。
===== 可変長引数 =====
引数リストに...を含めることで、可変長の引数を受け取ることを示す。...を前置した変数に配列として入る。
  <?php
  <?php
 
  function sum(...$numbers) {
namespace App;
     $acc = 0;
 
     foreach ($numbers as $n) {
use Illuminate\Database\Eloquent\Model;
         $acc += $n;
 
class Book extends Model
  {
     /**
      * Get the author that wrote the book.
      */
     public function author()
    {
         return $this->belongsTo('App\Author');
     }
     }
    return $acc;
  }
  }
echo sum(1, 2, 3, 4);
...の前に型宣言も付与できるが、その場合配列要素が全部その型が必要になる。
===== 名前付き引数 =====
PHP 8.0.0から名前付き引数が導入された。引数の位置、順番ではなく、名前ベースで渡せる。これにより、デフォルト値を持つ引数をスキップできるし、引数の順番を意識しなくてよくなる。


  $books = App\Book::all();
引数の名前の後にコロン:をつけたものを値の前につけて指定する。引数の名前には予約語も使える。ただし、変数など動的には指定できない。
 
 
  foreach ($books as $book) {
位置引数との混在もできる。その場合、名前付き引数は最後にする必要がある。
    echo $book->author->name;
<?php
  myFunction(paramName: $value);
array_foobar(array: $value);
PHP 8.1.0では、引数を...で展開した後に、名前付き引数も指定できる。ただし、展開済み引数の上書きはだめ。
  function foo($a, $b, $c = 3, $d = 4) {
  return $a + $b + $c + $d;
  }
  }
最初に全取得+1、N件のデータを取得+Nで、合計1+N回のクエリーが発行される。
var_dump(foo(...[1, 2], d: 40)); // 46
var_dump(foo(...['b' => 2, 'a' => 1], d: 40)); // 46
var_dump(foo(...[1, 2], b: 20)); // Fatal error. Named parameter $b overwrites previous argument


Eager loadによりこれを2回のクエリー発行で収めることができる。withメソッドを使う。
==== Return value ====
$books = App\Book::with('author')->get();
Ref: [https://www.php.net/manual/ja/functions.returning-values.php PHP: 戻り値 - Manual].
 
 
foreach ($books as $book) {
関数はreturn文で値を返せる。そこで処理を終了する。
    echo $book->author->name;
 
}
returnを省略した場合、nullを返す。
これは具体的には以下のSQLの実行になる。
 
select * from books
==== Variable Functions/可変関数/Callable/コールバック ====
 
 
select * from authors where id in (1, 2, 3, 4, 5, ...)
* [https://www.php.net/manual/ja/functions.variable-functions.php PHP: 可変関数 - Manual]
ただ、Eager loadすると、クエリーの数は減ってもモデルのアクセスが増えて、メモリーの使用量と実行速度が遅くなって、逆に悪化することがある ([https://www.reddit.com/r/laravel/comments/1adbjcc/laravel_eager_loading_can_be_bad/ Laravel - Eager loading can be bad! : r/laravel][https://blog.oussama-mater.tech/laravel-eager-loading-is-bad/ Laravel - Eager loading can be bad! | Personal Blog][https://dev.to/nicolus/how-the-new-one-of-many-laravel-relationship-made-my-project-600-times-faster-27ll How the new 'One Of Many' Laravel relationship made my project 600 times faster - DEV Community])
* [https://www.php.net/manual/ja/language.types.callable.php PHP: コールバック / Callable - Manual]
* [https://www.ycomps.co.jp/staffblog/11934 【PHP】コールバック関数サンプル3つをまとめる - ウェブ集客で企業を成功に導くホームページ制作会社|(株)ワイコム・パブリッシングシステムズ(福岡)]
* [https://pisuke-code.com/php-call-func-class-from-string/ PHPで関数やクラスを文字列から呼び出しする方法まとめ | PisukeCode - Web開発まとめ]
* [https://begien.com/article/29/view 【小ネタ】phpで変数でメソッドを実行する | BeginnerEngineerBlog]
* [https://zenn.dev/tanomu/articles/d92721f597e87f call_user_func() と $function() の動きが違った]
 
PHPで関数を引数で指定したり、変数として扱う仕組みがある。evalを使う必要はない。
 
可変関数は、関数を文字列で実行する仕組み。これとは別で、Callableという型がある。
 
可変関数は、関数名の文字列の変数に丸括弧を追加したら実行できるというもの。インスタンス変数があれば、メソッドもできる。
 
PHP 7.0から、関数のみ"str"()も可能になった。「[https://www.php.net/manual/ja/migration70.php PHP: PHP 5.6.x から PHP 7.0.x への移行 - Manual]」に記載はないが、パース方法が変わったことが由来の模様。
 
関数もメソッドも統一的に扱うものとして、Callable型がある。
 
CallableはPHPで関数を引数として渡したり、関数名の文字列を渡して、動的に関数を実行する仕組み。
 
callable型で表す。関数だけでなく、メソッドやstaticメソッドも対応できる。方法が2種類ある。
 
# 関数: 関数名の文字列。
# メソッド: 配列で指定。0番目の要素に、インスakeタンスやオブジェクト。1番目の要素にメソッド名の文字列で指定する。
# staticメソッド:  配列で指定。0番目の要素に、クラス名を指定する。'ClassName::methodName' 形式でも指定可能。
 
=====anonymous/無名関数=====
callableの型。非常に重要。
// "use" がない場合
$example = function () {
    var_dump($message);
};
$example();
// $message を引き継ぎます
$example = function () use ($message) {
    var_dump($message);
};
useを指定した場合だけ、親のスコープから変数を引き継げる。変数は関数定義時の値。


特に、hasManyの関係で、1対1じゃなくて、複数のモデルが入っている場合。


Laravel 8.42からLatestOfManyなどのメソッドが追加されており、これを使えば回避できる。これを使わない場合、JOINとMAXなどを駆使する必要があった。


ひとまず、hasManyとか、性能を見て、問題あったらeager loadingしたり工夫することにする。
===Classes and Objects===
====The Basics====
Ref: [https://www.php.net/manual/en/language.oop5.basic.php PHP: The Basics - Manual].
=====class=====
class内には変数 (プロパティー)、定数、関数 (メソッド) を含められる。


====== Constraining Eager Loads ======
class内の関数などで、これらのプロパティー、メソッド類の参照時は、$this->経由で参照する必要がある。


=== Testing ===
C系言語であれば、$this->相当は省略できたが、PHPでは指定が必要なので注意する。
=====::class=====
<className>::classでクラス名の完全修飾子の文字列を取得できる。


==== Database Testing ====
例外の試験など、クラス名の情報が必要な時によくみかける。


* [https://laravel.com/docs/5.8/database-testing Database Testing - Laravel 5.8 - The PHP Framework For Web Artisans]
PHP 8.0.0からオブジェクトに対しても::classを使用でき、元のクラス名を取得できる。その場合、get_class()と同じ。同じならPHP 7で使えないのでget_class()でいいか。
* [https://nebikatsu.com/8431.html/ 簡単!Laravelで日本語の仮データをDBに自動生成(factoryとseeder) - ネビ活 | ネットビジネス生活]
====Property====
Ref: [https://www.php.net/manual/ja/language.oop5.properties.php PHP: プロパティ - Manual].


シーダーでデータの自動登録方法を整理した。ただ、たくさんデータを登録してチェックする場合、ダミーデータを自動生成したい。Factoryというのでダミーデータを作成できる。
クラスのメンバー変数のことをプロパティー (property) とPHPでは呼んでいる。


===== Generating Factories =====
クラス内で、1以上のキーワード (アクセス権、static、PHP 8.1.0以後のみreadonly) のあとに、オプション型宣言 (PHP 7.4以後、readonly以外) の後に変数宣言を続ける。
  php artisan make:factory PostFactory
  public $var1
database/factoriesに生成される。
static $var2
var $var3
staticなど、アクセス権を指定しない場合、publicとデフォルトでみなされる。なお、varキーワードを使う方法もある。これはPHP4までのプロパティーの宣言方法。PHP5以後はpublicと同じ意味になる ([https://stackoverflow.com/questions/1206105/what-does-php-keyword-var-do What does PHP keyword 'var' do? - Stack Overflow])。


--modelオプションで使用するモデル (テーブル名) を指定しておくと、テンプレートも作成してくれる。
宣言時に初期値を代入もできるが、初期値は定数のみ。関数類は使用不能。
*[https://stackoverflow.com/questions/40827870/constant-expression-contains-invalid-operations php - Constant expression contains invalid operations - Stack Overflow]
*[https://qiita.com/H40831/items/15ebfbf7d9c05001b6df 【PHP】クラスプロパティの値には、動的な値を代入することができないようです。 #error - Qiita]
以下のエラーが出る。
PHP Fatal error: Constant expression contains invalid operations in /ぼくのかんがえたさいきょうのクラス.php on line 5
関数類で動的に代入したい場合、__constructでやる。
====Autoloading Classes====
Ref:
*[https://www.php.net/manual/en/language.oop5.autoload.php PHP: Autoloading Classes - Manual]
*[https://www.php.net/manual/en/function.spl-autoload-register.php PHP: spl_autoload_register - Manual]
別のファイルのクラスを使う方法の話。
#require_once()/require()/include: シンプルなファイル読み込み。PHP 4から。
#__autoload(): 非推奨。PHP 5.0で登場。
#spl_autoload_register(): PHP標準。PHP 5.1.0で登場。
#composer autoload: composer。
C系言語であれば、includeなどで外部ファイルをそのまま自分のファイルに読み込む。PHPでもrequire_onceなどで似たようなこともできる。が、PHPではこれをクラスごとに記述するのが煩雑だとして、自動で読み込む仕組みがいくつかある。


=== Other ===
GNU socialでも <https://notabug.org/gnusocialjp/gnusocial/src/main/lib/util/framework.php> でspl_autoload_registerを使っている。


==== Facade ====
基本的にはcomposerのautoloadかPHP標準のspl_autoload_registerの2択になっている。
[https://qiita.com/minato-naka/items/095f2a1beec1d09f423e 【Laravel】ファサードとは?何が便利か?どういう仕組みか? #初心者 - Qiita]


インスタンスメソッドをstaticメソッドのように使うための仕組み。ややこしいだけなのでいらないかなと思う。
基本的な使用方法。<syntaxhighlight lang="php">
<?php
spl_autoload_register(function ($class_name) {
    include $class_name . '.php';
});


==== Controllerの共通処理 ====
$obj  = new MyClass1();
$obj2 = new MyClass2();
?>


* [https://stackoverflow.com/questions/29343176/common-logic-between-various-laravel-controllers-method php - Common logic between various Laravel controllers method - Stack Overflow]
</syntaxhighlight>MyClass1.php MyClass2.phpから該当クラスを自動読み込みする。
* [https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11254710971 Laravelの実装について、複数のControllerから扱... - Yahoo!知恵袋]


例えば、/city/nycで都市都市の一覧表示。/city/nyc/streetで該当都市の通りの一覧表示。こういうタイプの処理を都市ごとに実装するというようなことがよくある。
該当クラスを使おうとしたときに、spl_autoload_registerに登録した関数が呼ばれる模様。


ただし、表示処理は共通処理があったりする。
spl_autoload_registerは、指定した関数を__autoload()の実装として登録する。順番に登録する。
spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): bool
callback: <code>callback(string $class): void</code> 。重要。nullを指定するとデフォルトのspl_autload()が登録される。$classにはクラスの完全修飾子が入る。


この共通処理をどうまとめて実装するか?いくつか方法がある。
このcallback内で独自のrequire_once相当をいろいろ指定する形になる。


* 親Controller
==== スコープ定義演算子 (::) ====
* ファサード/トレイト (関数クラス)/サービスクラス
* middleware
* モデルに共通処理を定義して呼び出し。
* validation/FormRequest


Controllerの前後に挟み込む感じならmiddleware、Controllerの処理の中でやりたいなら親Controllerか、ファサード/トレイト/サービスクラス、DB周りならModel?
* [https://www.php.net/manual/ja/language.oop5.paamayim-nekudotayim.php PHP: スコープ定義演算子 (::) - Manual]
* [https://www.php.net/manual/ja/language.oop5.late-static-bindings.php PHP: 遅延静的束縛 (Late Static Bindings) - Manual]
* [https://qiita.com/kouki_o9/items/5fd652ce6c7322480089 [PHP]staticメソッドとstatic::に関するメモ #初心者 - Qiita]


基本はmiddlewareの模様。ビジネスロジックになるなら、サービスクラスを使うのがいい。
スコープ定義演算子 (::) はトークンの一つ。定数、staticプロパティー、staticメソッド、親クラスなどにアクセスできる。


==== Coding Style Guide ====
[Paamayim Nekudotayim] とも呼ぶ。ダブルコロンを意味するヘブライ語らしい。


* [https://laravel.com/docs/11.x/contributions Contribution Guide - Laravel 11.x - The PHP Framework For Web Artisans]
staticメソッド/プロパティーは、遅延静的束縛 (Late Static Bindings) でアクセス可能。


PSR-2/4に準拠。
* MyClass::CONST_VALUE/$classname::CONST_VALUE;
* self::$my_static
* parent::CONST_VALUE
* static: 実行時に最初の呼び出しクラスを参照。


ローカル変数名やプロパティーの記法の指定がない。
staticは少々ややこしい。基本はself::でよいと思う。
====Traits====
[https://www.php.net/manual/ja/language.oop5.traits.php PHP: トレイト - Manual]


* [https://github.com/laravel/framework/blob/1a1a61068bc3c5594376a559e424ae09ec3fe64a/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php#L31 framework/src/Illuminate/Foundation/resources/exceptions/renderer/components/trace.blade.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework]
コード再利用のための仕組み。単一継承言語で、コードを再利用するための仕組み。関数クラス (デリゲート) 的なもの。クラスに関数クラスのメソッドを取り込める。インスタンス生成などはできず、関数を水平方向で構成可能にする。継承しなくても、メンバーに追加できる。
* [https://github.com/laravel/framework/blob/1a1a61068bc3c5594376a559e424ae09ec3fe64a/src/Illuminate/Foundation/Console/ServeCommand.php#L26 framework/src/Illuminate/Foundation/Console/ServeCommand.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework]
<?php
trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}
?>


Laravel本体のソースコードを見るとcamelCaseになっているのでこれに合わせる。
==== Magic/マジックメソッド ====
[https://www.php.net/manual/ja/language.oop5.magic.php PHP: マジックメソッド - Manual]


Bladeファイル名 [[https://stackoverflow.com/questions/61808504/laravel-naming-convention-for-blade-files Laravel naming convention for blade files - Stack Overflow]]
PHPのデフォルトの動作を上書きする特別なメソッドをマジックメソッドと呼んでいる。


チェインケースかcamelCaseを推奨。特に決まってはいない。
どらも__ (アンダーバー2個) から始まる。__始まりの全メソッドはPHPで予約されているのでユーザー定義メソッドとしては非推奨。


「[https://github.com/laravel/framework/blob/1a1a61068bc3c5594376a559e424ae09ec3fe64a/src/Illuminate/Foundation/resources/exceptions/renderer/components/theme-switcher.blade.php framework/src/Illuminate/Foundation/resources/exceptions/renderer/components/theme-switcher.blade.php at 1a1a61068bc3c5594376a559e424ae09ec3fe64a · laravel/framework]」にあるように、Laravel本体はチェインケースなので、チェインケースがよいだろう。
以下がある。


component類のslot名はcamelCase [[https://github.com/laravel/framework/blob/7d26b7ee454a0ccc339db92a641487f668b44331/tests/View/Blade/BladeComponentTagCompilerTest.php#L105 framework/tests/View/Blade/BladeComponentTagCompilerTest.php at 7d26b7ee454a0ccc339db92a641487f668b44331 · laravel/framework]]。
* __construct
* __destruct
* __call
* __callStatic
* __get
* __set
* __isset
* __unset
* __sleep
* __wakeup
* __serialize
* __unserialize
* __toString
* __invoke
* __set_state
* __clone
* __debugInfo


== CakePHP ==
__construct/__destruct/__clone以外の全マジックメソッドはpublic必須。E_WARNINGの警告が発生する。


=== Basic ===
__construct/__desctructは戻り値型を宣言してはいけない。


==== Structure ====
====Other====
=====クラス名の取得=====
*[https://stackoverflow.com/questions/19901850/how-do-i-get-an-objects-unqualified-short-class-name php - How do I get an object's unqualified (short) class name? - Stack Overflow]
*[https://www.php.net/manual/ja/function.get-class.php PHP: get_class - Manual]
*[https://qiita.com/miriwo/items/7972261a710e79dd1fd5 PHP クラス名::classはどういう処理?? #初心者 - Qiita]
*[https://www.php.net/manual/ja/language.oop5.basic.php#language.oop5.basic.class.class PHP: クラスの基礎 - Manual]
get_class($object);
クラス名::class
$object::class // PHP 8.0以上 (get_class相当)
(new \ReflectionClass($obj))->getShortName();
基本は名前空間付きのフルパスでの取得。クラス名だけだとgetShortName()


* bin: cakeコマンド類。
=== Reserved ===
* config: CakePHPの全体設定にかかるファイル群。
* logs
* plugins
* src
* tests
* tmp
* vendor
* webroot: アプリケーションルート。


===== Convention =====
==== keywords ====
Ref: [https://book.cakephp.org/3/ja/intro/conventions.html CakePHP の規約 - 3.10].
[https://www.php.net/manual/ja/reserved.keywords.php PHP: キーワードのリスト - Manual]


テーブル名は小文字、複数形、アンダーバー区切り。
式や関数ではなく、定数、クラス名、関数名として使えず、PHPで予約されている特別なキーワードがいくつかある。


===== Routing =====
statement/文に近い扱い。言語構文の一部扱い。
Ref: [https://book.cakephp.org/3/ja/development/routing.html#index-0 ルーティング - 3.10].
{| class="wikitable"
|+PHP のキーワード
|[[/www.php.net/manual/ja/function.halt-compiler.php|__halt_compiler()]]
|[[/www.php.net/manual/ja/language.oop5.abstract.php|abstract]]
|[[/www.php.net/manual/ja/language.operators.logical.php|and]]
|[[/www.php.net/manual/ja/function.array.php|array()]]
|[[/www.php.net/manual/ja/control-structures.foreach.php|as]]
|-
|[[/www.php.net/manual/ja/control-structures.break.php|break]]
|[[/www.php.net/manual/ja/language.types.callable.php|callable]]
|[[/www.php.net/manual/ja/control-structures.switch.php|case]]
|[[/www.php.net/manual/ja/language.exceptions.php|catch]]
|[[/www.php.net/manual/ja/language.oop5.basic.php#language.oop5.basic.class|class]]
|-
|[[/www.php.net/manual/ja/language.oop5.cloning.php|clone]]
|[[/www.php.net/manual/ja/language.oop5.constants.php|const]]
|[[/www.php.net/manual/ja/control-structures.continue.php|continue]]
|[[/www.php.net/manual/ja/control-structures.declare.php|declare]]
|[[/www.php.net/manual/ja/control-structures.switch.php|default]]
|-
|[[/www.php.net/manual/ja/function.die.php|die()]]
|[[/www.php.net/manual/ja/control-structures.do.while.php|do]]
|[[/www.php.net/manual/ja/function.echo.php|echo]]
|[[/www.php.net/manual/ja/control-structures.else.php|else]]
|[[/www.php.net/manual/ja/control-structures.elseif.php|elseif]]
|-
|[[/www.php.net/manual/ja/function.empty.php|empty()]]
|[[/www.php.net/manual/ja/control-structures.declare.php|enddeclare]]
|[[/www.php.net/manual/ja/control-structures.alternative-syntax.php|endfor]]
|[[/www.php.net/manual/ja/control-structures.alternative-syntax.php|endforeach]]
|[[/www.php.net/manual/ja/control-structures.alternative-syntax.php|endif]]
|-
|[[/www.php.net/manual/ja/control-structures.alternative-syntax.php|endswitch]]
|[[/www.php.net/manual/ja/control-structures.alternative-syntax.php|endwhile]]
|[[/www.php.net/manual/ja/function.eval.php|eval()]]
|[[/www.php.net/manual/ja/function.exit.php|exit()]]
|[[/www.php.net/manual/ja/language.oop5.basic.php#language.oop5.basic.extends|extends]]
|-
|[[/www.php.net/manual/ja/language.oop5.final.php|final]]
|[[/www.php.net/manual/ja/language.exceptions.php|finally]]
|[[/www.php.net/manual/ja/functions.arrow.php|fn]] (PHP 7.4 以降)
|[[/www.php.net/manual/ja/control-structures.for.php|for]]
|[[/www.php.net/manual/ja/control-structures.foreach.php|foreach]]
|-
|[[/www.php.net/manual/ja/functions.user-defined.php|function]]
|[[/www.php.net/manual/ja/language.variables.scope.php|global]]
|[[/www.php.net/manual/ja/control-structures.goto.php|goto]]
|[[/www.php.net/manual/ja/control-structures.if.php|if]]
|[[/www.php.net/manual/ja/language.oop5.interfaces.php|implements]]
|-
|[[/www.php.net/manual/ja/function.include.php|include]]
|[[/www.php.net/manual/ja/function.include-once.php|include_once]]
|[[/www.php.net/manual/ja/language.operators.type.php|instanceof]]
|[[/www.php.net/manual/ja/language.oop5.traits.php#language.oop5.traits.conflict|insteadof]]
|[[/www.php.net/manual/ja/language.oop5.interfaces.php|interface]]
|-
|[[/www.php.net/manual/ja/function.isset.php|isset()]]
|[[/www.php.net/manual/ja/function.list.php|list()]]
|[[/www.php.net/manual/ja/control-structures.match.php|match]] (PHP 8.0 以降)
|[[/www.php.net/manual/ja/language.namespaces.php|namespace]]
|[[/www.php.net/manual/ja/language.oop5.basic.php#language.oop5.basic.new|new]]
|-
|[[/www.php.net/manual/ja/language.operators.logical.php|or]]
|[[/www.php.net/manual/ja/function.print.php|print]]
|[[/www.php.net/manual/ja/language.oop5.visibility.php|private]]
|[[/www.php.net/manual/ja/language.oop5.visibility.php|protected]]
|[[/www.php.net/manual/ja/language.oop5.visibility.php|public]]
|-
|[[/www.php.net/manual/ja/language.oop5.properties.php#language.oop5.properties.readonly-properties|readonly]] (PHP 8.1.0 以降) *
|[[/www.php.net/manual/ja/function.require.php|require]]
|[[/www.php.net/manual/ja/function.require-once.php|require_once]]
|[[/www.php.net/manual/ja/function.return.php|return]]
|[[/www.php.net/manual/ja/language.variables.scope.php|static]]
|-
|[[/www.php.net/manual/ja/control-structures.switch.php|switch]]
|[[/www.php.net/manual/ja/language.exceptions.php|throw]]
|[[/www.php.net/manual/ja/language.oop5.traits.php|trait]]
|[[/www.php.net/manual/ja/language.exceptions.php|try]]
|[[/www.php.net/manual/ja/function.unset.php|unset()]]
|-
|[[/www.php.net/manual/ja/language.namespaces.php|use]]
|[[/www.php.net/manual/ja/language.oop5.properties.php|var]]
|[[/www.php.net/manual/ja/control-structures.while.php|while]]
|[[/www.php.net/manual/ja/language.operators.logical.php|xor]]
|[[/www.php.net/manual/ja/language.generators.php|yield]]
|-
|[[/www.php.net/manual/ja/language.generators.syntax.php#control-structures.yield.from|yield from]]
|
|
|
|
|}
<nowiki>*</nowiki> <code>readonly</code> は、関数名として使用できます。
{| class="wikitable"
|+コンパイル時の定数
|[[/www.php.net/manual/ja/language.constants.predefined.php|__CLASS__]]
|[[/www.php.net/manual/ja/language.constants.predefined.php|__DIR__]]
|[[/www.php.net/manual/ja/language.constants.predefined.php|__FILE__]]
|[[/www.php.net/manual/ja/language.constants.predefined.php|__FUNCTION__]]
|[[/www.php.net/manual/ja/language.constants.predefined.php|__LINE__]]
|[[/www.php.net/manual/ja/language.constants.predefined.php|__METHOD__]]
|-
|[[/www.php.net/manual/ja/language.namespaces.nsconstants.php|__NAMESPACE__]]
|[[/www.php.net/manual/ja/language.constants.predefined.php|__TRAIT__]]
|
|
|
|
|}


config/routes.phpでルーティングの基本が設定される。ここのコードで、後述のMVCがある程度自動設定されている。
==== Interfaces ====
[https://www.php.net/manual/ja/reserved.interfaces.php PHP: 定義済みのインターフェイスとクラス - Manual]


ほかに、アプリケーショントップの/アクセス時の処理を行う、重要な設定もここで行う。/はMVCの命名規則になっていないので、ここは手動設定?的なことが必要になる。基本はここがLoginになる。
===== stdClass =====
[https://www.php.net/manual/ja/class.stdclass.php PHP: stdClass - Manual]


Router::scopeとRouter::connectを使って処理する。
動的なプロパティーが使える、汎用的な空クラス。このクラス自体は、メソッドやプロパティーを持たない。


scopeはルーティングのグループ化。connectで個別に紐づける。<syntaxhighlight lang="php">
json_decodeなど一部の関数がこのインスタンスを返す。
Router::scope('/blog', ['plugin' => 'Blog'], function ($routes) {
// 型変換での作成。連想配列を(object)にキャストすると作れる。
    $routes->connect('/', ['controller' => 'Articles']);
(object) array('foo' => 'bar');
});
データベースの取得結果が、連想配列の他に、stdClassになっていることがある。
</syntaxhighlight>例えば、上記は/blogへのアクセスをArticlesController.index()に紐づける。
 
匿名オブジェクトや、動的プロパティーなどが主な利用方法。
 
データホルダーとして使う場合、連想配列のキーのほうが、自由度が高いので、そちらのほうが便利だと思われる。たくさんある配列関数も使えるし。
 
==Features==
Ref: [https://www.php.net/manual/en/features.php PHP: Features - Manual].
===Handling file uploads===
Ref: [https://www.php.net/manual/en/features.file-upload.php PHP: Handling file uploads - Manual].
 
input type="file"などのアップロードファイルのPHPでの処理方法・作法がある<syntaxhighlight lang="html">
<!-- データのエンコード方式である enctype は、必ず以下のようにしなければなりません -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
    <!-- MAX_FILE_SIZE は、必ず "file" input フィールドより前になければなりません -->
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    <!-- input 要素の name 属性の値が、$_FILES 配列のキーになります -->
    このファイルをアップロード: <input name="userfile" type="file" />
    <input type="submit" value="ファイルを送信" />
</form>
</syntaxhighlight>PHP側では$_FILES['userfile']に必要な情報が格納される。
*;<var>[[/wiki.gnusocial.jp//www.php.net/manual/ja/reserved.variables.files.php|$_FILES['userfile']['name']]]</var>
*:クライアントマシンの元のファイル名。
*;<var>[[/wiki.gnusocial.jp//www.php.net/manual/ja/reserved.variables.files.php|$_FILES['userfile']['type']]]</var>
*:ファイルの MIME 型。ただし、ブラウザがこの情報を提供する場合。 例えば、<code>"image/gif"</code> のようになります。 この MIME 型は PHP 側ではチェックされません。そのため、 この値は信用できません。
*;<var>[[/wiki.gnusocial.jp//www.php.net/manual/ja/reserved.variables.files.php|$_FILES['userfile']['size']]]</var>
*:アップロードされたファイルのバイト単位のサイズ。
*;<var>[[/wiki.gnusocial.jp//www.php.net/manual/ja/reserved.variables.files.php|$_FILES['userfile']['tmp_name']]]</var>
*:アップロードされたファイルがサーバー上で保存されているテンポラ リファイルの名前。
*;<var>[[/wiki.gnusocial.jp//www.php.net/manual/ja/reserved.variables.files.php|$_FILES['userfile']['error']]]</var>
*:このファイルアップロードに関する [[/wiki.gnusocial.jp//www.php.net/manual/ja/features.file-upload.errors.php|エラーコード]]
*;<var>[[/wiki.gnusocial.jp//www.php.net/manual/ja/reserved.variables.files.php|$_FILES['userfile']['full_path']]]</var>
*:ブラウザからアップロードされたファイルのフルパス。 この値は実際のディレクトリ構造を反映しているとは必ずしも言えないため、 信用できません。 PHP 8.1.0 以降で利用可能です。
tmp_nameが非常に重要。これをリネームする形で保存する。あとはnameも保存時のファイル名で重要。<syntaxhighlight lang="php">
<?php
$uploaddir = '/var/www/uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
 
echo '<pre>';
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
    echo "File is valid, and was successfully uploaded.\n";
} else {
    echo "Possible file upload attack!\n";
}
 
echo 'Here is some more debugging info:';
print_r($_FILES);
 
print "</pre>";
 
?>
</syntaxhighlight>上記がイメージ。


connectの第二引数にいろいろパラメーターを指定できて、actionのメソッドを指定できたりする。
DBに保存する場合は「[https://qiita.com/NULLchar/items/7bdc6685be0aa909e8fe PHPとMySQLを利用した画像・動画のアップロード・保存・表示 #PHP - Qiita]」も参考になる。
===Using PHP from the command line===
Ref: [https://www.php.net/manual/en/features.commandline.php PHP: Command line usage - Manual].


==== Controller ====
簡単なコードの確認などでPHPをコマンドラインなどから簡単に実行したいことがよくある。いくつか方法がある ([https://www.php.net/manual/en/features.commandline.usage.php PHP: Usage - Manual])。
AppControllerを継承して定義する。indexメソッドを最低限実装しておく。
#phpコマンドの引数にファイルを指定: <code>php file.php</code>/<code>php -f file.php</code>
#phpコマンドの引数kにコードを指定: <code>php -r 'print_r(get_defined_constants());'</code>
#phpコマンドに標準入力で読み込み: <code>php <file.php</code>
標準入力が一番使いやすく感じる。
==Function Reference==
===Affecting PHP's Behavior===
====Error Handling====
[https://www.php.net/manual/ja/book.errorfunc.php PHP: エラー処理 - Manual]


XXXControllerのXXXの小文字部分がURLになっていて、これでアクセスするとindexが発動する。
=====Runtime Configuration=====
PHPのエラー設定を整理する。 PHPのエラー設定は「[https://www.php.net/manual/en/errorfunc.configuration.php PHP: Runtime Configuration - Manual]」で一覧化されている。


メソッド名が、パスになっていて、それで表示される。このパスメソッドを一般的にacitonと呼んでいる。
xmlrpc_errors, syslog.facility, syslog.ident以外はどこでも設定可能。


$this->request
特に重要なのが以下の設定。
{| class="wikitable"
!設定
!初期値
!説明
|-
|error_reporting
|<code>E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED</code>
|エラー出力レベルを設定する。開発時にはE_ALL (2147483647/-1) にしておくとよい。
|-
|display_errors
|"1"
|エラーのHTML出力への表示を設定する。"stderr"を指定すると,stderrに送る。デフォルトで有効なのでそのままでいい。
|-
|display_startup_errors
|"0"
|PHPの起動シーケンス中のエラー表示を設定する。デバッグ時は有効にしておいたほうがいい。
|-
|log_errors
|"0"
|エラーメッセージのサーバーのエラーログまたはerror_logへの記録を指定する。これを指定しないとログが残らないため,常に指定したほうがいい
|-
|error_log
|NULL
|スクリプトエラーが記録されるファイル名を指定する。syslogが指定されると,ファイルではなくシステムロガーに送られる。Unixではsyslog(3)で,Windowsではイベントログになる。指定されていない場合,SAPIエラーロガーに送信される。ApacheのエラーログかCLIならstderrになる。
|}基本的に以下をphp.ini/.user.iniに設定しておけばよい。<syntaxhighlight lang="ini">
error_reporting = E_ALL
display_startup_errors = on
log_errors = on


リクエストに関する情報が入っている。PHPの$_FILESとか$_POSTなどを使わなくも、これを使えばよくなる。
; For file
display_errors = stderr
</syntaxhighlight>htpd.conf/.htaccessの場合は以下。<syntaxhighlight lang="http">
php_value error_reporting -1
php_flag display_startup_errors on
php_flag log_errors on


2次元配列にもなっているが、1次元配列部分はプロパティーとしてもアクセスできる。
# For file
php_value display_errors stderr
</syntaxhighlight>


* params: 送信されたすべてが入っている。
===== logrotate =====
* data: POSTのボディー。
* query: URLクエリー。
* url
* base
* webroot
* here:


特にdataをよく使うだろう。
* ライブラリー
** [https://github.com/cesargb/php-log-rotation cesargb/php-log-rotation: PHP Class to rotate log files]
* 自前
** [https://kuranabe.hatenablog.com/entry/2018/02/24/163500 【PHP】ログローテート対応のログ出力関数を実装する - kuranabeの開発備忘録]
* logrotate
** [https://stackoverflow.com/questions/14145812/how-to-configure-logrotate-with-php-logs linux - How to configure logrotate with php logs - Stack Overflow]
** [https://trueman-developer.blogspot.com/2020/02/php.html PHPのログロ-テーション設定]
** [https://qiita.com/shotets/items/e13e1d1739eaea7b1ff9 ログローテートソフトウエア logrotate についてまとめ #Linux - Qiita]
** [https://groworks.jp/blog/5763 PHPで効果的なログファイル管理を実現する方法 | 新潟のホームページ制作|Web制作会社 グローワークス]


===== Component =====
出力したログファイルがストレージを圧迫しないように、一定サイズ・期間でリネームして、最大保持数を維持したりする。
Ref: [https://book.cakephp.org/3/ja/controllers/components.html コンポーネント - 3.10].


コントローラー間で共有されるロジックのパッケージ。認証など処理を共通化できる。
いくつか方法がある。
$this->loadComponent('コンポーネント');
これでコンポーネントを読み込めて、以降は$this->コンポーネントでインスタンスにアクセスできる。


==== View ====
* GNU/Linuxのlogrotateコマンド
Ref: [https://book.cakephp.org/3/ja/views.html ビュー - 3.10].
* logrotateライブラリー
* 自前実装


Controllerで表示もできるものの、Viewで表示部分をメインにすることもできる。
やることは決まっているのだから、自前で実装してもいいかもしれない。


Viewはビューテンプレートとレイアウトで大きく構成される。
# ログ出力時
# ログ出力ファイルのサイズを確認して、設定サイズより大きければ、循環。
# 最古のログファイルを削除して、順番にリネーム。
# 最後に出力。


テンプレートは実際のページ表示用。レイアウトは、テンプレートを含み、ヘッダー、フッターなどをまとめている。
それだけ。
const LOG_DIRECTORY = '/var/log/logdir/';          // ログディレクトリ
const LOG_FILENAME  = 'logfname.log';              // ログファイル名
const LOG_FILEPATH  = LOG_DIRECTORY.LOG_FILENAME;  // ログのファイルパス
const MAX_LOTATES  = 3;                            // ログファイルを残す世代数
const MAX_LOGSIZE  = 1024*1024;                    // 1ファイルの最大ログサイズ(バイト)
function WriteLog($strlog){
    // 保存先ディレクトリを作成
    if(!file_exists(LOG_DIRECTORY)){
        mkdir(LOG_DIRECTORY);
    }
    // ログのローテート
    if(@filesize(LOG_FILEPATH) > MAX_LOGSIZE){
        // 最古のログを削除
        @unlink(LOG_FILEPATH.strval(MAX_LOTATES));
        // ログをリネーム .log → .log_01
        for ($i = MAX_LOTATES - 1; $i >= 0; $i--) {
            $bufilename = ($i == 0) ? LOG_FILEPATH : LOG_FILEPATH.strval($i);
            @rename($bufilename, LOG_FILEPATH.strval($i+1));
        }
    }
    // ログ出力
    file_put_contents(LOG_FILEPATH, date('y-m/d-H:i:s ').$strlog."\n", FILE_APPEND | LOCK_EX);
}
もう少しいい実装方法はありそう。


レイアウトを用意して、その中にビューテンプレートを埋め込んで表示するイメージ。これにより、ページ全体でレイアウトを統一しやすくなる。
=== Database ===


テンプレートファイルのデフォルトの拡張子は.ctp (CakePHP Templateの略)。
==== PDO ====


===== Template =====
* [https://www.php.net/manual/ja/book.pdo.php PHP: PDO - Manual]
src/TemplateがViewテンプレート。ここにコントローラー名に対応したディレクトリーを作って、そこにViewテンプレートを作成する。
* [https://qiita.com/mpyw/items/b00b72c5c95aac573b71 PHPでデータベースに接続するときのまとめ #MySQL - Qiita]


例えば、src/Template/Helloにindex.ctpを用意する。
===== Introduction =====
PHP Data Objects。PHPからDBへのアクセスの軽量で高性能なインターフェイス。DBの全関数を実行できるわけではない。DBアクセスの抽象化レイヤーを提供する。つまり、DBが何であろうが、同じ関数でSQLの発行、データ取得ができる。


Controller側で、public $autoRender = true;があると、テンプレートを使う。命名規則で自動的に。
===== Connections =====


そして、$this->viewBuilder()->autoLayout(false) でレイアウトの使用可否を設定する。
====== Open ======
PDOインスタンスの作成で接続ができる。
<?php
try {
    $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
} catch (PDOException $e) {
    // たとえば、タイムアウトしたあとに再接続を試みます
}
?>
引数にDSNと、ユーザー名、パスワードを指定する。


index.ctpはテンプレートなので、ここにPHPコードを記入してもOK。
PDOインスタンスが存在する間、接続が継続する。


テンプレート内では変数が使える。この変数は、コントローラーで$this->setで設定されたものになる。
[https://www.php.net/manual/ja/pdo.setattribute.php PHP: PDO::setAttribute - Manual]


基本的には以下のようなphpコードで埋め込む。
オプションを設定できる。
<?php echo $variable; ?>
<?= $variable ?>


===== Layout =====
====== Close ======
デフォルトで使用するレイアウトは、src/Template/Layout/default.ctpにある。
接続終了時には、明示的にオブジェクトを破棄する必要がある。具体的には、変数にnullを代入する。
<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
// ここで接続を使用します
$sth = $dbh->query('SELECT * FROM foo');
// 使用を終了したので、閉じます
$sth = null;
$dbh = null;
?>
nullにしない場合、script終了時に閉じられる。


ここにファイルを追加することが、レイアウトの作成になる。
ただ、通常はすぐに単体のscriptは終了するので、いちいち書く必要はない。


レイアウト内で、テンプレートを表示する関数類がある。
===== Query =====
SQLを実行するためのPDOメソッドがいくつかある。


* $this->fetch('content'): ビューテンプレート
* [https://www.php.net/manual/ja/pdo.exec.php PHP: PDO::exec - Manual]: 引数のSQLを実行して、影響のある行数を返す。結果のいらないSQLやSELECT以外のINSERT/UPDATEなどで使う。
* [https://www.php.net/manual/ja/pdo.query.php PHP: PDO::query - Manual]: 引数のSQLを実行して、実行結果をPDOStatementで取得する。ユーザー入力を伴わない固定SQLで使う。似たようなSQLをループなどで複数回実行するならprepareのほうが性能が向上する。
* [https://www.php.net/manual/ja/pdo.prepare.php PHP: PDO::prepare - Manual]: [https://www.php.net/manual/ja/pdostatement.execute.php PHP: PDOStatement::execute - Manual]用のSQL文を用意する。プレースホルダーを用意する。プリペアードステートメントというSQLの機能を使う。ユーザー入力をエスケープしたり、一部分だけ異なるようなSQLがキャッシュされて効率が上がる。


Controller側では、$this->viewBuilder()->layout('Hello');でレイアウトを指定できる。
===== prepared statement =====
[https://www.php.net/manual/ja/pdo.prepared-statements.php PHP: プリペアドステートメントおよびストアドプロシージャ - Manual]


===== Element =====
重要。パラメーターマーク/プレースホルダーを配置したSQL文を用意して、後でプレースホルダーに変数をバインドしてSQLを実行する。
レイアウトよりも小さいパーツ。ボタンなど、流用するようなものをElementとして用意できる。


$this->element(エレメント名, [キー=>値,...])で表示できる。
プレースホルダーとして、名前付きパラメーターと疑問符パラメーターの2種類がある。


src/Template/Element内に作成する。エレメント名.ctpで作成する。キー=>値の関係で、パラメーターを渡せる。
* 名前付きパラメーター: [:name] の形式で配置する。バインド時は名前。数が多い場合。バインド時は先頭の:は省略可能。compact関数で短縮できる。
* 疑問符パラメーター: [?] を配置する。バインド時は0開始の番号。数が少ない場合シンプル。PHP 7.4.0以上で??で?自体をエスケープ。


Controllerのaction内でsetで、layout内で使用する変数を指定できる。これで、layout-element間で変数を渡せる。
なお、名前付きと疑問符は混在できない。プレースホルダーには、SQLのデータリテラルのみを配置できる。SQLの文などはだめ。


==== Model ====
プレースホルダーはデータリテラル全体に適用が必要。つまり、LIKEの%は値に含める必要がある。代わりに、引用符がいらない。
DBを処理する部分。
<?php
/* 値の配列を渡してプリペアドステートメントを実行する */
$sql = 'SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour';
$sth = $dbh->prepare($sql, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
$sth->execute(['calories' => 150, 'colour' => 'red']);
$red = $sth->fetchAll();
/* 配列のキーの前にも、コロン ":" を付けることができます(オプション) */
$sth->execute([':calories' => 175, ':colour' => 'yellow']);
$yellow = $sth->fetchAll();
?>


===== 具体的には、テーブルクラス (テーブルへのアクセス処理) とエンティティークラス (テーブルから取り出した1行分のデータのための列の表現) で実現する。 =====
<?php
CakePHP内では、モデル名Table/モデル名でそれぞれ命名する。テーブルクラスは複数形、エンティティークラスは単数形で扱う。テーブル自体も複数形。
/* 値の配列を渡してプリペアドステートメントを実行する */
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < ? AND colour = ?');
$sth->execute([150, 'red']);
$red = $sth->fetchAll();
$sth->execute([175, 'yellow']);
$yellow = $sth->fetchAll();
?>


CakePHPでDBにアクセスする際には、まず設定ファイルにDBの情報を用意する。Config/app.phpのDatasourcesのdefaultに入力する。
$stmt->execute([1 => $gender, 0 => $city]);
疑問符の場合、キーを指定すれば、順番を変更してもOK。


モデル自体は、src/Model内に配置する。EntityとTableディレクトリーでそれぞれ用意する。
====== PDOStatement::execute ======
[https://www.php.net/manual/ja/pdostatement.execute.php PHP: PDOStatement::execute - Manual]


継承するだけで、基本的な動作は機能する。
プリペアードステートメントを実行する。実行にあたって、プレースホルダーに値を埋め込む必要がある。


Controllerから、これらのモデルにアクセスする。
# bindValue/bindParamを使用。
# 引数で配列で指定。ただし、NULL以外、値は全てPDO::PARAM_STR扱い。既存のbindValue/bindParamを全上書き。


DBへのアクセスにはORMのQueryBuilderとSQLベースのConnectionManagerがある。
型が全部PARAM_STRなら問題ない。それ以外の数値などを含めたいなら、bindValueでしたほうがいい。


===== ConnectionManager =====
====== PDOStatement::bindValue/bindParam ======
Ref: [https://book.cakephp.org/3/ja/orm/database-basics.html データベースの基本 - 3.10].
プリペアードステートメント内で、部分的に後からプレースホルダーに値を割り当てる。


SQLをそのまま発行するタイプのクラス。
* bindValue: 呼び出し時に値で埋め込まれる。
$conn = ConnectionManager::get('設定名');
* bindParam: execute実行時に変数が評価される。
$statement = $conn->execute('SQL');
$statement->fetchAll('num' | 'assoc');
ConnectionManagerからConnectionインスタンスを取得し、DBオブジェクトとして扱う。


executeでステートメントを取得し、statementのメソッド呼び出しのタイミングでSQLが実行される。
基本はbindValueでよい。が、bindParamも出番がある。
<?php
$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (?, ?)");
$stmt->bindParam(1, $name);
$stmt->bindParam(2, $value);
// 行を挿入します
$name = 'one';
$value = 1;
$stmt->execute();
// パラメータを変更し、別の行を挿入します
$name = 'two';
$value = 2;
$stmt->execute();
?>
変数だけ変えて、複数回実行する場合。


実行結果は配列。numを指定すると、キーが数字、assocだと項目名 (カラム名) がキーになる。基本はassocを指定するとよい模様。
ただ、この場合、executeの引数で渡してもいい。が、引数だと全部PDO::PARAM_STRになってしまうので、bindParamも役立つ。


===== TableRegistry =====
[http://hono-wp.seesaa.net/article/411183815.html PDOで複数回SQLを実行: コツコツ学ぶWordPress、技術メモ]
Ref: [https://book.cakephp.org/3/en/orm/saving-data.html Saving Data - 3.10].
<?php
  use Cake\ORM\TableRegistry;
require("db_info.php");
$dsn = 'mysql:host=localhost;dbname='.$database.';charset=utf8';
try {
  $dbh = new PDO($dsn, $username, $password,
    array(
      PDO::ATTR_EMULATE_PREPARES =>false,
      PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"
    )
  );
} catch (PDOException $e) {
  exit('データベース接続失敗。'.$e->getMessage());
}
date_default_timezone_set('Asia/Tokyo');
$result = array();
$st = $dbh->prepare('select city,TRUNCATE(sum(setai)/3,0) as setai,TRUNCATE(sum(man)/3,0) as man,TRUNCATE(sum(woman)/3,0) as woman,TRUNCATE(sum(total)/3,0) as total from jinko where ym in (:ym1,:ym2,:ym3) group by city');
$ym1 = date('Y/m', strtotime(date('Y-m-1').' -1 month'));
$ym2 = date('Y/m', strtotime(date('Y-m-1').' -2 month'));
$ym3 = date('Y/m', strtotime(date('Y-m-1').' -3 month'));
$st->bindValue(':ym1', $ym1 , PDO::PARAM_STR);
$st->bindValue(':ym2', $ym2 , PDO::PARAM_STR);
$st->bindValue(':ym3', $ym3 , PDO::PARAM_STR);
$st->execute();
$result = $st->fetchAll(PDO::FETCH_ASSOC);
$ym1 = date('Y/m', strtotime(date('Y-m-1').' -1 year -1 month'));
$ym2 = date('Y/m', strtotime(date('Y-m-1').' -1 year -2 month'));
$ym3 = date('Y/m', strtotime(date('Y-m-1').' -1 year -3 month'));
$st->bindValue(':ym1', $ym1 , PDO::PARAM_STR);
$st->bindValue(':ym2', $ym2 , PDO::PARAM_STR);
$st->bindValue(':ym3', $ym3 , PDO::PARAM_STR);
$st->execute();
  $result = array_merge($result,$st->fetchAll(PDO::FETCH_ASSOC));
   
   
  // Prior to 3.6 use TableRegistry::get('Articles')
  echo json_xencode($result);
$articlesTable = TableRegistry::getTableLocator()->get('Articles');
$article = $articlesTable->get(12); // Return article with id 12
   
   
  $article->title = 'CakePHP is THE best PHP framework!';
  ?>
$articlesTable->save($article);
ただ、bindValueをまたやってもいい。だったら、bindParamは余計になくてもいいか?
組み込まれていないテーブルはgetで取得する必要がある。
 
===== Fetch =====
PDOStatementからデータを取得するメソッドが複数ある。
 
* [https://www.php.net/manual/ja/pdostatement.fetch.php PHP: PDOStatement::fetch - Manual]
* [https://www.php.net/manual/ja/pdostatement.fetchall.php PHP: PDOStatement::fetchAll - Manual]
* [https://www.php.net/manual/ja/pdostatement.fetchcolumn.php PHP: PDOStatement::fetchColumn - Manual]
* [https://www.php.net/manual/ja/pdostatement.fetchobject.php PHP: PDOStatement::fetchObject - Manual]
 
====== FETCH_MODE ======
 
* [https://www.php.net/manual/ja/pdo.setattribute.php PHP: PDO::setAttribute - Manual]
 
* [https://www.php.net/manual/ja/pdostatement.fetch.php PHP: PDOStatement::fetch - Manual]
* [https://www.php.net/manual/ja/pdo.constants.php PHP: 定義済み定数 - Manual]
 
PDOのコンストラクターのオプション、setAttribute、fetchの引数で取得条件を指定できる。
 
* PDO::FETCH_BOTH: 規定。カラム名と0開始の添え字の配列で返す。
* PDO::FETCH_NAMED: 同名のカラムが複数ある場合、値の配列を返す。
* PDO::FETCH_NUM: 0開始のカラム番号の配列で返す。


===== Basic =====
同名カラムがある場合、PDO::FETCH_NUM/FETCH_NAMEDじゃないと取れない。
 
=== Calendar ===
[https://www.php.net/manual/ja/refs.calendar.php PHP: 日付および時刻関連 - Manual]
 
==== time/速度計測 ====
 
* [https://qiita.com/HidakaRintaro/items/8893f1a36709eee3582f PHPの処理速度の計測方法 #PHP7 - Qiita]
* [https://www.php.net/manual/ja/function.hrtime.php PHP: hrtime - Manual]
* [https://www.php.net/manual/ja/function.microtime.php PHP: microtime - Manual]
* [https://www.php.net/manual/ja/intro.hrtime.php PHP: はじめに - Manual]
 
PHPで処理速度などを計測したいことがある。基本は処理前後のタイムスタンプの差分で、どの言語でも共通の論理だが、いくつか方法がある。
 
microtimeとhrtimeの2個の関数をタイムスタンプの取得で使える。hrtimeはPHP 7.3.0以上で使用可能。単位ナノ秒。問題なければ、こちらが推奨されている。HRTime (High Resolution Time) の拡張のモジュールと関係する関数とのこと。


====== Query logging ======
両方とも、引数にtrueを指定して、floatで取得するのが基本。
Ref: [https://book.cakephp.org/3/en/orm/database-basics.html#query-logging Database Basics - 3.10].
<?php
  // クエリーログを有効
$start = hrtime(true); // 計測開始時間
  $conn->logQueries(true);
// 計測したい処理
$end = hrtime(true); // 計測終了時間
// 終了時間から開始時間を引くと処理時間になる
echo '処理時間:'.($end - $start).'ナノ秒' . PHP_EOL;
?>
PHPのバージョンを気にするのが嫌なので、ラップするとよい。
<?php
  /**
  * Time target function.
  * @param callable $callback Target function.
  * @return int|float Run time [ns].
  */
  function timeit(callable $callback)
{
    $time = 'microtime';
    $nanoFactor = 1000;
    if (function_exists('hrtime')) {
        $time = 'hrtime';
        $nanoFactor = 1;
    }
    $start = $time(true);
    $callback();
    $stop = $time(true);
    return ($stop - $start) * $nanoFactor;
}
   
   
  // クエリーログを停止
echo timeit(function(){sleep(1)});
  $conn->logQueries(false);
CakePHPのクエリービルダーの実際のSQLを確認できる。
  // one liner.
(function($c){$s=hrtime(true);$c();return hrtime(true)-$s;})(function(){});
  (function($c){$s=hrtime(true);$c();return hrtime(true)-$s;})(function(){sleep(1)});
?>
こんな感じ。
 
===File system===
Ref: [https://www.php.net/manual/ja/refs.fileprocess.file.php PHP: ファイルシステム - Manual].
====File system====
=====file=====
*[https://www.php.net/manual/ja/function.file-get-contents.php file_get_contents] — ファイルの内容を全て文字列に読み込む
*[https://www.php.net/manual/ja/function.file-put-contents.php file_put_contents]: データをファイルに書き込む。戻り値に書き込みバイト数を返す。失敗したらfalse。成否は完全一致===falseで。
*[https://www.php.net/manual/ja/function.file.php PHP: file - Manual]: ファイル全体を読み込んで改行区切りで配列にする。
上記2個の非常に重要な入出力関数がある。
 
バイナリーやHTTP GETに対応している。アップロードされたファイルの読み込みなどでお世話になる。
 
file_get_contents/file_put_contentsはfopen/fwrite/fcloseの一連のファイル処理を含んでいるので非常に簡単。
 
===== directory =====
 
* [https://www.php.net/manual/ja/function.mkdir.php PHP: mkdir - Manual]:
* rmdir
 
=====check=====
*[https://www.php.net/manual/ja/function.file-exists.php PHP: file_exists - Manual]
*[https://www.php.net/manual/ja/function.is-readable.php PHP: is_readable - Manual]
*[https://www.php.net/manual/ja/function.is-writable.php PHP: is_writable - Manual]
入出力とセットで使うファイルの不在確認の関数群。
 
===== path =====
 
* [https://qiita.com/okdyy75/items/cc9e99025345bb17c37b PHPのディレクトリまとめ #PHP - Qiita]
* [https://www.ajisaba.net/php/pathinfo.html PHP・ファイル名・パスなどの取得]
* [https://zenn.dev/phpbeginners/articles/a237fda03dd970 PHPの便利な関数:ファイルパス関連dirname(), basename(), realpath(), getcwd(), chdir()]
* [https://www.php.net/manual/ja/language.constants.magic.php PHP: マジック定数 - Manual]
* [https://www.php.net/manual/ja/function.basename.php PHP: basename - Manual]: パスからファイル名を取得。
* [https://www.php.net/manual/ja/function.dirname.php PHP: dirname - Manual]: パスからディレクトリーの取得。
* [https://www.php.net/manual/ja/function.realpath.php PHP: realpath - Manual]: 相対パスを絶対パスに変換。
* [https://www.php.net/manual/ja/function.getcwd.php PHP: getcwd - Manual]: 現在のディレクトリー。__DIR__は実行中ディレクトリー。includeしたときにgetcwdは実行元、__DIR__はinclude先。
* [https://www.php.net/manual/ja/function.chdir.php PHP: chdir - Manual]
* __DIR__: ファイルの存在するディレクトリー。終端スラッシュなし。
パス関係の操作。重要。
 
==== Directory ====
 
===== Constants =====
[https://www.php.net/manual/ja/dir.constants.php PHP: 定義済み定数 - Manual]
 
* DIRECTORY_SEPARATOR: Windowsなら\、それ以外/らしい。
このDIRECTORY_SEPARATORは基本的には使うことはない。
 
* [https://stackoverflow.com/questions/26881333/when-to-use-directory-separator-in-php-code path separator - When to use DIRECTORY_SEPARATOR in PHP code? - Stack Overflow]
* [https://stackoverflow.com/questions/625332/is-using-the-directory-separator-constant-necessary/7032949#7032949 language agnostic - Is using the directory separator constant necessary? - Stack Overflow]
 
理由として、Windowsがパスの文脈で/も受け入れるから。必要な場面は、OSからパスを受け取る場合に、\が返ってくることがあり、そこでexplodeしたりしたい場合のみ。
 
自分からパスを指定する文脈では、/で問題なく、DIRECTORY_SEPARATORを使う必要はない。
 
===== ディレクトリー以下のファイル一覧 =====
 
* [https://www.php.net/manual/ja/function.glob.php PHP: glob - Manual]
* [https://www.php.net/manual/ja/function.scandir.php PHP: scandir - Manual]
* [https://qiita.com/katsukii/items/ec816b23f68b6dfa0f87 PHPで任意のディレクトリ下にあるファイルを一覧取得する方法 #PHP - Qiita]
* [https://next-code.jp/blog/tech/php/php/ PHP 最強に簡単なファイル一覧取得方法 - 株式会社NextCode - 福山市のHP制作・システム開発]
* [https://stackoverflow.com/questions/18290498/which-is-better-to-read-files-from-a-directory-using-php-glob-or-scandir-o Which is better to read files from a directory using PHP - glob() or scandir() or readdir()? - Stack Overflow]
* [https://laboratory.kazuuu.net/using-phps-glob-function-to-count-the-number-of-files-in-a-folder/ PHPのglob()関数を使用しフォルダ内のファイルを数える | Men of Letters(メン・オブ・レターズ) – 論理的思考/業務改善/プログラミング]
* [https://stackoverflow.com/questions/5483181/php-most-efficient-way-to-list-the-files-in-a-very-large-directory scandir - PHP most efficient way to list the files in a very large directory - Stack Overflow]
* [https://stackoverflow.com/questions/2763290/which-is-faster-glob-or-opendir php - Which is faster: glob() or opendir() - Stack Overflow]
* [https://www.phparch.com/2010/04/putting-glob-to-the-test/ Putting glob() to the test | php[architect]]
* [https://stackoverflow.com/questions/2922954/getting-the-names-of-all-files-in-a-directory-with-php Getting the names of all files in a directory with PHP - Stack Overflow]
 
方法がいくつかある。
 
* glob
* scandir
* SPL


「[https://qiita.com/cranpun/items/09be1b43f115305e108e [cakephp] Queryオブジェクトから実行したSQL生成 #MySQL - Qiita]」にあるように、他に、sql()やgetValueBinder()->bindings();でも取得できる。
ソートや簡易検索が必要かどうかで、速度や適切な方法が異なる。
        $this->log($this->MBase->findMBase($options)->sql());


====== データの取り出しと結果セット ======
ただ、使いやすいのはglob。
Ref: [https://book.cakephp.org/3/ja/orm/retrieving-data-and-resultsets.html#finder データの取り出しと結果セット - 3.10].
glob(string $pattern, int $flags = 0): array|false
マッチする・ファイル、ディレクトリーの配列を返す。マッチしなければ空の配列。失敗はfalse。


hydrate(false)でfindなどした際の結果を、オブジェクトではなく配列にできる。
patternはlibcのglobのルールでマッチする。


==== Migration ====
* *: 0以上の任意文字。
* ?: 任意の1文字。
* [...]: グループの1文字。先頭が!の場合、否定。
* \: エスケープ。
 
flags。特に重要なのは以下。
 
* GLOB_ONLYDIR: ディレクトリーのみ。
* GLOB_NOSORT: デフォルトでアルファベット順でソートしているのを無効にする。これを指定すると速くなる。
./..以外の全ファイルのマッチ。
array_merge(glob('.[!.]*'), glob('*'));
 
===International===
====mbstring====
=====mb_substr=====
Ref:
Ref:
*[https://www.php.net/manual/ja/function.mb-substr.php PHP: mb_substr - Manual].
*[https://www.php.net/manual/ja/function.substr.php PHP: substr - Manual]
mb_substr(
    string $string,
    int $start,
    ?int $length = null,
    ?string $encoding = null
): string
substr同様、lengthにはマイナス値を指定可能。その場合、末尾からの文字数になる。
省略するかnullを指定すると、全文字。0は0文字。
===Text===
====Strings====
=====strpos=====
Ref: [https://www.php.net/manual/ja/function.strpos.php PHP: strpos - Manual].
strpos([https://www.php.net/manual/ja/language.types.string.php string] <code>$haystack</code>, [[/wiki.gnusocial.jp//www.php.net/manual/ja/language.types.string.php|string]] <code>$needle</code>, [[/wiki.gnusocial.jp//www.php.net/manual/ja/language.types.integer.php|int]] <code>$offset</code> = 0): [[/wiki.gnusocial.jp//www.php.net/manual/ja/language.types.integer.php|int]]|[[/wiki.gnusocial.jp//www.php.net/manual/ja/language.types.value.php|false]]
文字列 <code>haystack</code> の中で、 <code>needle</code> が最初に現れる位置を探します。
include相当。よく使う。
戻り値に注意。有無の確認時は、strpos() !== falseの厳密一致でチェックする必要がある。


* [https://book.cakephp.org/migrations/3/ja/index.html Migrations - 3.x]
===== echo/print/printf =====
* [https://qiita.com/ozawan/items/8144e02ca70519f3dcaf CakePHP3のマイグレーションまとめ #cakephp3 - Qiita]
* [https://book.cakephp.org/phinx/0/en/index.html Phinx Documentation - 0.13]


PHPファイルでDBのスキーマ変更を行うための仕組み。VCSでDB設定を管理でき、コマンドでDBの設定などを行える利点がある。
* [https://www.php.net/manual/ja/function.echo.php PHP: echo - Manual]
* [https://www.php.net/manual/ja/function.print.php PHP: print - Manual]
* [https://www.php.net/manual/ja/function.printf.php PHP: printf - Manual]
* [https://qiita.com/tadsan/items/e09475093bc336881b20 echoとprintの違い #PHP - Qiita]
* [https://qiita.com/chimayu/items/dcc443c3a99f3379b3da PHP : echoとprintの違い #まとめ - Qiita]
* [https://inouelog.com/php-echo-print/ 【PHP】echoとprintの違いとは?どっちを使えば良い?]


CakePHPでは、Phinxをマイグレーションに使っており、コマンド類はPhinxのラッパーになっている。細かいことはPhinxにあたる必要がある。
PHPの出力関数群。よく使うが、扱いが特殊なので整理する。


config/Migrationsディレクトリーに、マイグレーションファイルを配置し、以下のmigrationsコマンドの実行でDBにテーブルを作成できる。
まず、echo/printは関数ではなく、if/forなどと同じ言語構造、キーワード扱い。丸括弧はなくてもいい。紛らわしいのでないほうがいい。
bin/cake migrations migrate
bin/cake migrations rollback
戻す場合はrollback。


マイグレーションファイルは、config/Migrationディレクトリーで、YYYYmmddHHMMSS_MigrationName.phpというように、作成日を入れて用意する。
* 共通: 末尾に改行は付与されない。自分で"\n"を指定必要。関数ではないので丸括弧は不要。
* echo: 戻り値void。式ではないので、if returnなどで使えない。戻り値がない分printよりわずかに速い。文字数が短い。コンマ区切りで複数列挙可能。文字列連結するよりコンマ区切りのほうが.演算子の優先順位など扱いが簡単。HTMLでの埋め込みに便利な <?= ?>の短縮表記 (<?php echo ; ?>相当)もあり。
* print: 戻り値intで常に1を返す。ifや条件演算子の結果部分など、式の文脈で使用可能。


自分で手作業でマイグレーションファイルを作成できるが、bakeコマンドでひな形を用意できる。これを使ったほうがおそらくいい。
基本はechoでいい。if return/条件演算子など戻り値や式が必要な箇所でだけprintを使う。


===== マイグレーションファイル =====
printfは関数。書式指定が必要ならこれ。


====== Valid Column Types ======
=== Basic/Vartype/変数・データ型関連 ===
<code>Phinx</code> で一般的に利用可能なフィールドの型は次の通り:


* string
==== Variable ====
* text
* integer
* biginteger
* float
* decimal
* datetime
* timestamp
* time
* date
* binary
* boolean
* uuid


このほかに、以下も可能。
===== print_r/var_export/var_dump =====


In addition, the MySQL adapter supports <code>enum</code>, <code>set</code>, <code>blob</code>, <code>tinyblob</code>, <code>mediumblob</code>, <code>longblob</code>, <code>bit</code> and <code>json</code> column types (<code>json</code> in MySQL 5.7 and above). When providing a limit value and using <code>binary</code>, <code>varbinary</code> or <code>blob</code> and its subtypes, the retained column type will be based on required length (see [[/book.cakephp.org/phinx/0/en/migrations.html#limit-option-and-mysql|Limit Option and MySQL]] for details);
* [https://qiita.com/yasumodev/items/4b0e5a0d2d1ec3b3177b [PHP] print_r、var_dump、var_export のちがい #PHP - Qiita]
* [https://laracasts.com/discuss/channels/laravel/how-to-create-dynamically-create-configcustomphp-config-file How to create Dynamically create config/custom.php config file]
* [https://www.php.net/manual/ja/function.print-r.php PHP: print_r - Manual]
* [https://www.php.net/manual/ja/function.var-export.php PHP: var_export - Manual]
* [https://www.php.net/manual/ja/function.var-dump.php PHP: var_dump - Manual]


In addition, the Postgres adapter supports <code>interval</code>, <code>json</code>, <code>jsonb</code>, <code>uuid</code>, <code>cidr</code>, <code>inet</code> and <code>macaddr</code> column types (PostgreSQL 9.3 and above).
PHPの変数の内容を、配列やオブジェクトなどの複雑なデータ型でも表示、出力可能なのがprint_r/var_export/var_dump。微妙に違いがある。


====== 既存のテーブルにカラムを追加 ======
* var_dump: これのみ型情報がある。
以下のコマンドでカラム追加を含むコードを作成できる。
* var_export: 変数の文字列表現。config.phpなどファイルに出力すればそのままinclude可能。
bin/cake bake migration AddPriceToProducts price:decimal
* print_r: 基本は閲覧用の文字列。
* print_r/var_export: 第二引数をtrueにすると、画面出力ではなく、戻り値に返す。


$text = "<?php\n\nreturn " . var_export($myarray, true) . ";";
使うとしたら、var_exportとvar_dumpだと思われる。
var_dumpは型の情報が詳しい。詳細な情報が欲しい場合、var_dump。そうでない、単に見たいだけなら、var_exportで十分。
var_dumpは表示のみ。出力制御関数を使えば、文字列に保存はできるが、基本はデバッグ表示用。
  <?php
  <?php
  use Migrations\AbstractMigration;
  $data = array(
    "A" => "Apple",
    "B" => "Banana",
    "C" => "Cherry"
);
echo "---print_r---\n";
print_r($data);
echo "---var_export---\n";
var_export($data);
echo "---var_dump---\n";
var_dump($data);
?>
 
---print_r---
Array
(
    [A] => Apple
    [B] => Banana
    [C] => Cherry
)
---var_export---
array (
  'A' => 'Apple',
  'B' => 'Banana',
  'C' => 'Cherry',
)
   
   
  class AddPriceToProducts extends AbstractMigration
   
  {
  ---var_dump---
    public function change()
array(3) {
    {
  ["A"]=>
        $table = $this->table('products');
  string(5) "Apple"
         $table->addColumn('price', 'decimal')
  ["B"]=>
              ->update();
  string(6) "Banana"
    }
  ["C"]=>
  string(6) "Cherry"
}
画面出力でデバッグしたいならば、前後でpre要素を出力させる。
<?php
function print_r2($val){
        echo '<nowiki><pre>';</nowiki>
         print_r($val);
        echo  '<nowiki></pre></nowiki>';
  }
  }
addColumnの3引数にいろいろパラメーターを指定できる。
?>
    public function change()
 
    {
===== var_exportの変数取込 =====
        $table = $this->table('m_grade');
var_exportの文字列表現。config.phpに出力して、Includeするほかに、テキストを変数にしたいことがある。
        $table->addColumn('image_data', 'blob', [
 
            'default' => null,
* [https://stackoverflow.com/questions/933506/how-to-read-output-of-var-export-into-a-variable-in-php import - how to read output of var_export into a variable in PHP? - Stack Overflow]
            'limit' => Phinx\Db\Adapter\MysqlAdapter::BLOB_MEDIUM,
* [https://tips.recatnap.info/laboratory/detail/id/575 reCatnap: php var_export()した後の文字列を元に戻す(eval())]
            'null' => true,
 
            'after' => 'image_path',
素直にevalする。
        ]);
$dumpStr = var_export($var,true);
        $table->update();
eval("$somevar = $dumpStr;");
    }
くれぐれも入力に注意する。
afterで直前の列を指定できる。
 
=== Process ===
 
==== exec ====
システムプログラムの実行のための関数群がある。Windowsだとcmd.exe経由で実行される。
 
===== exec/shell_exec/system/passthru =====
 
* [https://stackoverflow.com/questions/7093860/php-shell-exec-vs-exec shell - PHP shell_exec() vs exec() - Stack Overflow]
* [https://www.php.net/manual/ja/function.exec.php PHP: exec - Manual]
* [https://www.php.net/manual/ja/function.passthru.php PHP: passthru - Manual]
* [https://www.php.net/manual/ja/function.shell-exec.php PHP: shell_exec - Manual]/[https://www.php.net/manual/ja/language.operators.execution.php PHP: 実行演算子 - Manual]
* [https://www.php.net/manual/ja/function.system.php PHP: system - Manual]
 
外部プログラム実行のための3の関数がある。違いがある。
 
* shell_exec: 標準出力をstringで返す。バッククオート演算子``と同じ。プログラムの成功可否、終了コードは判断不能。
* exec: 既定で標準出力の最後の1行のみ返す。第二引数に配列を指定すれば、行区切りの配列で返すこともできる。
* passthru: Unixコマンドの出力がバイナリーで、ブラウザーに直接バイナリーを返す場合に、exec/systemの代わりに使う。戻り値は?falseで標準出力に直接結果を出力する。
* system: C言語のsystem関数に類似。system()でコマンドを実行して出力する。成功時にコマンド出力の最終行を返す。出力をファイルや別のストリームにリダイレクトしないと、終了までPHPが止まる。
 
{| class="wikitable"
|+
!項目
!exec
!passthru
!shell_exec/``
!system
|-
|出力
| -
|x
| -
|x
|-
|終了コード
|x
|x
| -
|x
|-
|戻り値
|最終行
|null/false
|全行
|最終行
|-
|全行取得
|x
| -
|x
| -
|-
|用途
|外部コマンドの結果文字列取得 (終了チェックあり)
|画像応答
|外部コマンドの結果文字列取得
|文字応答
|}
基本はshell_exec/`` execで十分。
 
===== exec =====
[https://www.php.net/manual/ja/function.exec.php PHP: exec - Manual]
exec(string $command, array &$output = null, int &$result_code = null): string|false
戻り値は最終行。失敗したらfalseを返す。実行コマンドの終了コードは$result_codeに渡される。
 
===== escapeshellarg/escapeshellcmd =====
 
* [https://www.php.net/manual/ja/function.escapeshellarg.php PHP: escapeshellarg - Manual]
* [https://www.php.net/manual/ja/function.escapeshellcmd.php PHP: escapeshellcmd - Manual]
* [https://stackoverflow.com/questions/1881582/whats-the-difference-between-escapeshellarg-and-escapeshellcmd php - escapeshellarg と escapeshellcmd の違いは何ですか? - Stack Overflow]
 
exec/shell_exec/``と併用するエスケープ用関数。
 
* escapeshellarg: 引数の文字列を一重引用符で囲み、既存の一重引用符を苦オートする。これで、引数全体を1個の引数にする。複数の引数の誤り実行を回避できる。
* escapeshellcmd: シェルに特殊な意味のある&#;`|*?~<>^()[]{}$\\x0A のシェルの特殊文字にバックスラッシュを追加し、'"は対がない場合のみエスケープ。
 
元々、コマンド全体をエスケープする [escapeshellcmd] だけがあった。が、これだとコマンドの引数を追加する攻撃が可能になるので、 [escapeshellarg] が追加されたらしい ([https://blog.tokumaru.org/2012/04/php-escapeshellcmd-chase.html PHPのescapeshellcmdを巡る冒険 | 徳丸浩の日記])。
 
* [https://blog.tokumaru.org/2011/01/php-escapeshellcmd-is-dangerous.html PHPのescapeshellcmdの危険性 | 徳丸浩の日記]
* [https://blog.tokumaru.org/2011/01/escapeshellcmd-dangerous-usage.html#p01 escapeshellcmdの危険な実例 | 徳丸浩の日記]
* [https://blog.tokumaru.org/2016/12/phpescapeshellcmdmailcve-2016-10033.html PHPのescapeshellcmdを巡る冒険はmail関数を経てCVE-2016-10033に至った | 徳丸浩の日記]
* [https://www.docswell.com/s/ockeghem/58W1LZ-phpconf2016#p19 安全なPHPアプリケーションの作り方2016 | ドクセル]
* [https://blog.hamayanhamayan.com/entry/2021/12/14/222235 CTFのWebセキュリティにおけるCommand Injectionまとめ(Linux, Windows, RCE) - はまやんはまやんはまやん]
 
ただ、escapeshellcmdは、パラメーターインジェクションの危険性があるので、使ってはいけないらしい。
 
基本は引数に [escapeshellarg] を使うだけ。
 
===Other/その他の基本モジュール===
[https://www.php.net/manual/ja/refs.basic.other.php PHP: その他の基本モジュール - Manual]
====JSON====
[https://www.php.net/manual/ja/book.json.php PHP: JSON - Manual]
 
===== About =====
json_encode/json_decodeをよく使う。非常に重要。


====== Blobの対応 ======
json_encodeはオブジェクトをJSON文字列表記にできるのでデバッグなどで便利。


* [https://qiita.com/oppara/items/a903e174136049aa9137 CakePHP Migrations の limit オプションについて #PHP - Qiita]
[https://kohkimakimoto.hatenablog.com/entry/2012/05/17/180738 PHPでUnicodeアンエスケープしたJSONを出力する関数 - オープンソースこねこね]
* [https://qiita.com/1roh/items/0e40585214c26fbdf5cb cakephp3 の Migration で mediumblob を認識させる #MySQL - Qiita]


CakePHP自体は、MySQLのblobには直接は対応していない。binaryでひな形を作って、手動でlimitを変更する。
JSON_UNESCAPED_UNICODE をオプションに指定しないと日本語はユニコードエスケープ表記になる。


====== 列の更新 ======
===== 連想配列 =====
<blockquote>Updating columns name and using Table objects
json_decode(
    string $json,
    ?bool $associative = null,
    int $depth = 512,
    int $flags = 0
): mixed
json_decodeは第二引数にtrueを指定しないと、object (stdClass) になる。trueにすると、連想配列になる。基本は連想配列でいいと思う。


If you use a CakePHP ORM Table object to manipulate values from your database along with renaming or removing a column, make sure you create a new instance of your Table object after the <code>update()</code> call. The Table object registry is cleared after an <code>update()</code> call in order to refresh the schema that is reflected and stored in the Table object upon instantiation.
==== Other ====


https://book.cakephp.org/migrations/3/en/#updating-columns-name-and-using-table-objects</blockquote>
===== die/exit =====


===== updateした後に、CakePHPのORMを使う模様。 =====
* [https://www.php.net/manual/ja/function.die.php PHP: die - Manual]
          // Update exisiting column default row data from path.
* [https://www.php.net/manual/ja/function.exit.php PHP: exit - Manual]
        $mbaseTable = TableRegistry::get('m_base');
        foreach($mbaseTable->find() as $row) {
            $file_path = WWW_ROOT . $row->LOGO_PATH;
            if (is_readable($file_path)) {
                $raw_data = file_get_contents($file_path);
                if ($raw_data) {
                    $row->LOGO_DATA = $raw_data;
                    $mbaseTable->save($row);
                }
            }
        }
こういうイメージ。


===== Debug =====
dieはexitと完全に同等。
Ref:
exit(string $status = ?): void
exit(int $status): void
メッセージを表示してスクリプトを終了する。関数ではなく、言語構造扱い。


* [https://book.cakephp.org/3/ja/development/debugging.html デバッグ - 3.10]
statusを指定しない場合、丸括弧不要。status=0指定とみなされる。
* [https://www.sejuku.net/blog/28383 【CakePHP入門】デバッグ(debug)を使ってみよう! | 侍エンジニアブログ]


CakePHP関係のクラスであればlogメソッドがある。
statusが文字列なら、終了直前に表示する。intの場合、終了ステータス扱い。0-254。255は予約されている。0は正常終了。


他に、[[/book.cakephp.org/3/ja/core-libraries/logging.html#Cake%5CLog%5CLog::write|<code>Cake\Log\Log::write()</code>]]のstaticメソッドもある。
===== eval =====
[https://www.php.net/manual/ja/function.eval.php PHP: eval - Manual]


Log::debug('text')などで使う。
文字列をPHPコードとして評価する。危険なので、特にユーザーから入力を受け付ける場合は、注意する。できれば使わないほうがいい。


* pr: print_r+pre
evalで評価する文字列内でreturnした結果が返却値となる。ないならnull。戻り値が必要なら、テキスト内で忘れずにreturnする。
* debug:
* dd: debug+die
Log::debug()を使うと、logs/debug.logに出力される。


=== Topic ===
==Topic==
===HTTP===


==== Pagination ====
* [https://qiita.com/okdyy75/items/d21eb95f01b28f945cc6 PHPでPOST送信まとめ #PHP - Qiita]
コントローラーのpublic $paginateプロパティーに、ページネーションの設定を記載する。
* [https://qiita.com/shinkuFencer/items/d7546c8cbf3bbe86dab8 APIなどにfile_get_contents()を使うのはオススメしない理由と代替案 #PHP - Qiita]
* [https://www.php.net/manual/ja/function.file-get-contents.php PHP: file_get_contents - Manual]
* [https://www.php.net/manual/ja/book.curl.php PHP: cURL - Manual]


その後、$this->paginate(テーブル)で自動的にページネーションされたデータを取得できる。
PHPでHTTP通信をする方法がいくつかある。
*file_get_contents
*curl
file_get_contentsはPHP標準。curlは外部ライブラリー。


==== Test ====
[https://stackoverflow.com/questions/11064980/php-curl-vs-file-get-contents PHP cURL vs file_get_contents - Stack Overflow]」などを見る限り、GET以外はcurlのほうが速くて複雑なことができるらしい。
Ref: [https://book.cakephp.org/3/ja/development/testing.html テスト - 3.10].


CakePHPはPHPUnitでのテストに対応している。
file_get_contentsは元々ローカルや内部ファイルの読み込み用らしい。


まずDBをテスト用に置換する。config/app.phpのDatasourcesにtestを追加しておく。
GNU socialではHTTPClientクラス経由で実現するので、内部実装を意識する必要はない。


bakeコマンドの中で、fixtureとtestが関係するコマンド。
curlでのリクエスト方法を覚えておくと、汎用性が高い模様。


まずfixtureでテストデータを作成する。
==== cURL ====
cake bake fixture <model>
modelでMVCの一単位を指定する。


続いて以下のコマンド群でMVCのテストファイルを作成。
* [https://www.php.net/manual/ja/book.curl.php PHP: cURL - Manual]
cake bake test controller <Controller>
* [https://www.php.net/manual/ja/curl.examples-basic.php PHP: 基本的な curl の使用法 - Manual]
cake bake test entiity <Entity>
* [https://qiita.com/shinkuFencer/items/d7546c8cbf3bbe86dab8 APIなどにfile_get_contents()を使うのはオススメしない理由と代替案 #PHP - Qiita]
cake bake test table <nowiki><Table></nowiki>
testsディレクトリー内の以下に生成される。


* Fixture: テーブル名Fixture.php
基本的な使用方法。
* TestCase: クラス名Test.php


Test.php内で、以下のコードで該当Fixtureを自動読み込みする。
# curl_initでurlを指定してセッション初期化。
public $fixtures ['app.テーブル名'];
# curl_setopt/curl_setopt_arrayでオプションを設定。
TestCaseクラスがCakePHP特有で、これでうまくやっている模様。<syntaxhighlight lang="php">
## CURLOPT_POST=true/CURLOPT_POSTFIELDSでPOST関係指定。
namespace App\Test\TestCase\Model\Table;
## CURLOPT_RETURNTRANSFER=trueでcurl_execの応答ボディーをテキストで取得。
# curl_execで転送実行。CURLOPT_RETURNTRANSFER=trueを指定しない場合、true/falseのみ。
# curl_closeでセッション終了。


use App\Model\Table\ArticlesTable;
<?php
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
$ch = curl_init("<nowiki>http://www.example.com/</nowiki>");
$fp = fopen("example_homepage.txt", "w");
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);
// curl_setopt_array($ch, $options);
curl_exec($ch);
if(curl_error($ch)) {
    fwrite($fp, curl_error($ch));
}
$info    = curl_getinfo($ch);
$errorNo = curl_errno($ch);
curl_close($ch);
fclose($fp);
?>
<?php
/* curlセッションを初期化する */
$ch = curl_init();
/* curlオプションを設定する */
curl_setopt($ch, CURLOPT_URL, "<nowiki>http://www.google.com/</nowiki>");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
/* curlを実行し、その内容を$result変数に保存 */
$result = curl_exec($ch);
/* curlセッションを終了する */
curl_close($ch);
/* result変数に保存した内容を表示 */
echo htmlspecialchars($result);


class ArticlesTableTest extends TestCase
{
    public $fixtures = ['app.Articles'];


    public function setUp()
[https://www.php.net/manual/ja/curl.constants.php PHP: 定義済み定数 - Manual]
    {
        parent::setUp();
        $this->Articles = TableRegistry::getTableLocator()->get('Articles');
    }


    public function testFindPublished()
特に重要なオプションがいくつかある。
    {
        $query = $this->Articles->find('published');
        $this->assertInstanceOf('Cake\ORM\Query', $query);
        $result = $query->enableHydration(false)->toArray();
        $expected = [
            ['id' => 1, 'title' => 'First Article'],
            ['id' => 2, 'title' => 'Second Article'],
            ['id' => 3, 'title' => 'Third Article']
        ];


        $this->assertEquals($expected, $result);
* CURLOPT_POST: trueならHTTP POST (application/x-www-form-urlencoded)。
    }
* CURLOPT_POSTFIELDS: POSTのリクエストボディー。文字列で渡すか、連想配列。連想配列の値が配列の場合、Content-Type: multipart/form-dataになる。ファイル送信はCURLFile (ファイル名) かCURLStringFile (ファイルの中身)を使う。
}
* CURLINFO_HEADER_OUT => true: curl_getinfoにリクエストヘッダーを含める ([https://stackoverflow.com/questions/17092677/how-to-get-info-on-sent-php-curl-request web services - How to get info on sent PHP curl request - Stack Overflow])。
</syntaxhighlight>そして、setUp内で、まずはテーブルをメンバー変数に格納して、それを参照する形。


=== Error ===
[https://www.php.net/manual/ja/function.curl-getinfo.php PHP: curl_getinfo - Manual]


==== PHP message: PHP Warning:  file_put_contents(/var/www/html/logs/error.log) [<nowiki><a href='http://php.net/function.file-put-contents'>function.file-put-contents</a></nowiki>]: failed to open stream: Permission denied in /var/www/html/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php on line 133 ====
curl_getinfoで応答結果の詳細を確認できる。
起動してトップ画面を開くと、logs/error.logに以下のエラー。
2024/04/04 07:27:48 [error] 30#30: *1 FastCGI sent in stderr: "PHP message: PHP Warning:  file_put_contents(/var/www/html/logs/error.log) [<nowiki><a href='http://php.net/function.file-put-contents'>function.file-put-contents</a></nowiki>]: failed to open stream: Permission denied in /var/www/html/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php on line 133
PHP message: PHP Warning:  file_put_contents(/var/www/html/logs/error.log) [<nowiki><a href='http://php.net/function.file-put-contents'>function.file-put-contents</a></nowiki>]: failed to open stream: Permission denied in /var/www/html/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php on line 133
パーミッションの設定がDockerに不足している?


「[https://qiita.com/keisukeYamagishi/items/44163d2c6df002a87d5e CakePHP Error Warning: file_put_contents(/var/www/html/logs/error.log) #PHP - Qiita]」にあるように、
特に以下は重要。


docker exec -ti php_jaccs_auto bashでchmod a+w ./logs/*相当を実行すると解決した。ただし、事前にWindowsのExplorerで書き込みを許可しておく必要がある。git bashのchmodはWindowsには機能しない。
* http_code


==== Warning: Warning (512): SplFileInfo::openFile(/var/www/html/tmp/cache/persistent/myapp_cake_core_translations_cake_en__u_s) [<nowiki><a href='http://php.net/splfileinfo.openfile'>splfileinfo.openfile</a></nowiki>]: failed to open stream: Permission denied in [/var/www/html/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php, line 398] ====
===== JSONデータの送受信 =====
Ref: [https://blog.s-giken.net/383.html CakePHP3でWarning Error: SplFileInfo::openFile()エラーが発生した場合の対処方法 | エス技研].
[https://www.smartllc.jp/blog/20150811-how-to-post-json-in-php/ JSON形式のデータをPOST送受信する方法(PHP) | 合同会社スマート]


app.phpにmask=>0666を追加する。既存のキャッシュファイルは削除しておく。
# 連想配列でリクエストボディーのデータを作って、json_encodeでJSON文字列に変換。
# ヘッダーContent-Type指定: <code>curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));</code>
# リクエストボディー指定: curl_setopt($ch, CURLOPT_POSTFIELDS, $data_json);
# 戻り値のデコード: $res_json = json_decode($result , true );


dockerのアクセス権www-dataの問題の気がする。
以上。
$data = array(
'test1'=>'aaa',
'test2'=> array(
array(
'test3'=>'bbb'
)
),
'test4'=> array(
array(
'test5'=>'ccc',
'test6'=>'ddd'
)
)
);
$data_json = json_encode($data);
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_json);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, '<nowiki>http://posttestserver.com/post.php'</nowiki>);
$result=curl_exec($ch);
echo 'RETURN:'.$result;
curl_close($ch);
$result=curl_exec($ch);
$res_json = json_decode($result , true );
echo $res_json['return1'];
[[Category:PHP]]

2024年9月13日 (金) 17:51時点における最新版

Library

Framework

  • Symfony
  • CakePHP
  • FuelPHP: 2010年誕生。
  • Codeigniter: シンプル、軽量。
  • Zend
  • Laravel: 2011年誕生。
  • Phalcon
  • Yii - Wikipedia

Template

いろいろある。

  • Blade: Laravel標準。
  • DIV: 1ファイルでシンプル。大規模には向かない。
  • Smarty: 万能。
  • Twig: 拡張はしにくい。

使うとしたら、歴史の長いSmarty。

高速らしい。

そもそもテンプレートエンジンがいるのかどうかという議論がある。

UI/UXを突き詰めると、JavaScriptを使わざるを得ず、サーバーテンプレートエンジンは初回だけなので、いっそのことJSで全部やろうというのが最近の流れの模様。

PHP自体が一種のテンプレートエンジンという主張がある。が、関数をあれこれ書く必要があり、可読性が悪い。

SmartyよりTwigのほうが性能が上とか。

Volt: テンプレートエンジン — Phalcon 3.0.2 ドキュメント (Japanese / 日本語)」。高速フレームワークのPhalconではVoltを使っている。

Twig

Twig v3のほうが速いらしいが、Smarty v3のほうが速いというデータもある。

Smarty

Pure PHP/HTML views VS template engines views - Stack Overflow」が決定的だった。Smartyの開発者がSmartyのほうがTwigより速いと回答していた。2012年。Smartyでいいと思う。

ORM

Ref:

いろいろある。Doctrineが有名。

  • Doctrine: Symfonyで採用。有名。
  • Eloquent
  • Propel: Symfony v1.2で採用されていた。
  • PHP activerecord
  • PHPDAO
  • PDO: PHP標準。
  • Xyster

ただ、速度を優先する場合、PDOが最速になるらしい。

ORMは別になくてもいいか。

Migrate

  • Phinx
  • Doctrine Migrations

PHPで「Doctrine Migrations」を使ってみる

CakePHPに採用されているPhinxのほうが人気なのでPhinxを使ったほうがよいだろう。

Test

  • PHPUnit

Search

検索キーワードをフォームから受信後、DBにSQLで検索をかけて取得結果を返すのが基本。

それとは別に、検索用のアプリにリクエストを受け渡しして検索するという方法がある。どちらでもいけるような、ドライバーのライブラリーがある。

検索サービスで有名なのは以下。

Laravel Scoutでtntsearchを使う方法がある (Laravel Scout + TNTSearchによる小規模プロジェクトへの全文検索機能の追加 #PHP - Qiita/Laravel ScoutとTNTSearchを使用してサイト全文検索を実装してみる – helog)。

Packagist」の検索結果をみても、tntsearchが特に人気の模様。

About

About

PHPはプログラミング言語だ。汎用的なプログラミング言語だが、主にウェブサーバー上のソフトウェアで使用される。

GNU socialはPHPで記述されている。他にもWordPress・NextCloudなどがPHPで記述されている。これらのPHP製ソフトウェアはVPSだけではなく安価なレンタルサーバーでも動作するため、低コストで運用することができる。

PHPの公式リファレンスは日本語版があり、わかりやすくまとまっている。

ウェブ上にはPHPに関するTipsが多く公開されており、大抵の疑問はウェブ検索で解決できる。

Version

PHPは言語の版数が上がる際、過去の版と互換性の無い破壊的変更がなされることがある。

開発者はこのリスクを軽減するために、非推奨の言語機能を避け、実行時の警告 (warning) を適切に処理するべきだ。

GNU socialは現在PHP 7系で動作する様に記述されており、PHP 8系への対応は作業途中だ。

PHP v8

PHP v8になっていろいろ更新が入った。特にPHP v7.4からv8に更新する際のポイントがあるので整理する (行事: 「12月にPHP8.3が出るので、PHP8で増えた文法をおさらいしましょうセミナー」参加報告 | PHP8対応の肝は型とエラーレベル | GNU social JP Web)。

大きく以下2点がある。

  1. エラーレベルの上昇。
  2. 型の厳格化。

エラーレベルが1段階上がったため、今までWarningで問題なかったものがFatal Errorになって動作しなくなる。他に、型が厳格になっている。

具体的には、php.ini/.user.iniで以下を指定して、PHP v7.4時点で警告にできるだけ対応しておく。

error_reporting=E_ALL ; -1

続いて、phpソースファイルに以下を記入して型を厳密にしておく。

declare(strict_types=1);

チェックツールがあるのでこれを使うと問題箇所などがわかる。

  • PHP CodeSniffer
  • PHPStan
  • Rector

まず上記2個を試して、おまけでRectorも試すとよい。

Guide

Ref:

PHPのコーディングの推奨規約がある。PSR-12というのがメジャーな模様。GNU socialでも採用されている。

What should I name my PHP class file? - Stack Overflow」にあるように、PSRではファイル名には記載がない。PSR-4や「PSR Naming Conventions - PHP-FIG」に記載がある程度。

ただ、「Manual - Documentation - Zend Framework」、「CakePHP Conventions - 4.x」など、他の規約があり、クラス名と同じになっている。

PHPのクラス名は大文字小文字を区別しないが、わかりにくいので大文字小文字で、クラス名と一致させておくとよさそう。

ただし、viewなど、表示に直接結びついているものは、小文字でもいいかも。ファイル名とURLパスが同じほうが分かりやすい。

Tool

PHPをWebブラウザーで実行、動作確認のツールがいくつかある。

3v4l.orgがパーマリンクがあって、複数バージョンの動作確認できるので、これがいいと思う。

Package manager

Composer

PHPのパッケージ管理システム。PHP v5.3.2以上で動作する。

Install

Ref: インストール: Composer | モダンなPHPのパッケージマネージャー – senooken JP.

[ -e installer ] || wget https://getcomposer.org/installer
php installer --install-dir="$LOCAL/stow/$PKG-$VER/bin" --filename=$PKG

公式サイトからinstallerをダウンロードしてそれを使って任意の場所に設置する。

Usage

Composerを使う場合,composer.jsonファイルを用意する。このファイルはプロジェクトの依存関係を記載する。VCSで管理すべきファイルだ。

このファイルに使用するライブラリーを以下のように記入する。

<{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

composer.jsonに指定する最初の項目はrequireキーだ。このキーで依存パッケージをComposerに知らせる。パッケージ名とバージョンを指定する。

新規にパッケージを追加する場合は、以下のコマンドでインストールとcomposer.jsonへの追記を行えます。同時に、composer.lockファイルも作成されます。composer.lockも管理すべきファイル。

composer require "monolog/monolog:1.0.*"

パッケージ名はベンダー名とプロジェクト名から構成される。

1.0.*は1.0の任意のバージョンを示す。

composer.jsonを用意したら,以下のようにcomposerのinstallコマンドを実行する。

php composer.phar install

これにより,vendorディレクトリーにパッケージがインストールされる。

プロジェクトにgitを使っている場合,.gitignoreにvendorディレクトリーを追加したほうがいい。

Composerによるインストールが完了すると,composer.lockファイルにダウンロードしたパッケージとバージョンを出力する。composer.lockをプロジェクトリポジトリーに追加して,プロジェクトメンバー全員が同じバージョンのパッケージを使用する。

composer.lockが存在するプロジェクトで上記コマンドを実行する場合,composer.jsonの内容に加えて,composer.lockの内容も参照されて,composer.lockと同じバージョンがインストールされる。

パッケージを最新バージョンに更新したい場合,composer updateコマンドを使う。このコマンドを実行すると,最新バージョンをインストールして,composer.lockも更新する。動作としては,composer.lockを削除後にcomposer installを実行することと等しい。composer updateは基本的には使わない。

updateやinstallの後にパッケージ名を指定すると,指定したパッケージだけ更新やインストールできる。

composer update monolog/monolog

Composerでインストール可能な主なパッケージは,Packagistリポジトリーに配置されている。

自動読み込み (Autoloading)

ライブラリーの自動読み込みのために,Composerはvendor/autoload.phpファイルを生成する。以下のように,ライブラリーのクラスを使用するファイルの先頭でこのファイルを読み込めば使えるようになる。

<require __DIR__ . '/vendor/autoload.php';

$log = new Monolog\Logger('name');
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING));
$log->addWarning('Foo');

composer.jsonのautoload欄に指定することで,ライブラリー以外の自分のコードも自動読み込みの対象にできる。

composer.jsonを編集した場合、composer dump-autoloadを実行してvendor/autolaod.phpを必ず更新します。

Libraries

Libraries - Composer

自前のライブラリーをComposerでインストール可能な形式にする方法がある。

Every project is a package

ディレクトリーにcomposer.jsonがあると、そのディレクトリーはパッケージになる。プロジェクトとパッケージの違いは、名前の有無。プロジェクトは名前のないパッケージという扱いになる。

パッケージをインストール可能にするにあたって、composer.jsonに最低限名前が必要。

{
    "name": "acme/hello-world",
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

acme/hello-worldというプロジェクトになる。acmeはベンダー名で、ベンダー名は必須。

ベンダー名に迷う場合、GitHubのユーザー名が適している。パッケージ名は小文字必須。単語区切りは-にするのが慣例。

Library Versioning

VCSでパッケージを管理している場合、composerはVCSからバージョンを自動で判別する。VCSを使っていない場合だけ、versionプロパティーを追加する。

{
    "version": "1.0.0"
}
Publishing to a VCS

composer.jsonを用意したらVCSのリモートリポジトリーに公開する。ベンダー名とユーザー名は不一致でも問題ない。

公開したパッケージを取り込む場合、requireで指定する。

{
    "name": "acme/blog",
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/username/hello-world"
        }
    ],
    "require": {
        "acme/hello-world": "dev-master"
    }
}

パッケージ名hello-worldに必要なリポジトリーの情報をrepositoriesで指定している。たぶん、末尾のパッケージ名とリポジトリー名は一致が必要。

Publishing to packagist

VCSでの公開のケースは以上。ただ、repositoriesの情報は省略する方法がある。これは、Packagistに登録している場合。composerはpackagitstから同盟パッケージを探す。公開して問題ないなら、Packagistへの登録を検討する。

Light-weight distribution packages

.githubディレクトリーのように、パッケージに不要なファイルがある。

.gitattributesでパッケージやzipに含めないファイルを指定できる。

// .gitattributes
/demo export-ignore
phpunit.xml.dist export-ignore
/.github/ export-ignore

以下のコマンドで確認できる。

git archive branchName --format zip -o file.zip

パッケージに含まれないだけで、Gitリポジトリーには入っている。

Articles

Versions and constraints

Versions and constraints - Composer

composer requireなどで指定するパッケージのバージョンにはいくつか記法がある。このバージョン部分は、composerではversion constraint (バージョン制約) と呼んでいる。このバージョン制約で、チェックアウト対象を判断する。

~/my-library$ git branch
v1
v2
my-feature
another-feature
~/my-library$ git tag
v1.0
v1.0.1
v1.0.2
v1.1-BETA
v1.1-RC1
v1.1-RC2
v1.1
v1.1.1
v2.0-BETA
v2.0-RC1
v2.0
v2.0.1
v2.0.2
tag

基本的に、composerはタグを扱う。

上記のようなタグの場合、composerは先頭のvなどのプレフィクスを除外して考える。基本的にはこの中で、一番新しいものを優先的に探す。

branch

タグではなく、ブランチのチェックアウトが必要なら、特別なdev-*プレフィクス/サフィックスを指定してブランチを指定する。

上記の例で、my-featureブランチの指定が必要ならば、dev-my-featureを指定する。

ブランチ名がバージョン名と似ている場合、記法が変わる。v1.x-devのように指定する。v1タグではなく、v1ブランチを明示するために、.xは必須。あるいは、タグ名とブランチ名を完全に別の名前 (v1ブランチの代わりにv1.xブランチ) にしておけば、.xは不要。

バージョン名によく似たブランチ名を指定する場合だけ、dev-プレイフィクスではなく、-devサフィックスを指定する。

phpDocumentor

Ref: Home | phpDocumentor.

PHPのソースコードにコメントを残す際に、構文に従って記載すると、ツールで表示したり、文書に出力できたりする。ソースコードリーディングにも役立つので、積極的に記載したほうがよさそう。構文を整理しておく。

特に記法が大事。

ファイル冒頭の<?php の直後あたりに書くと、ファイルレベルのDocBlockになる。逆にclassの直前などに書くと、ファイル冒頭でもclassレベルになる。

DocBlockは3部構成。

  1. Summary=短い説明。改行直前の.か空行で終わり。
  2. Description=長い説明。アルゴリズムの機能や、使用方法、例など。最初のタグか、改行、DocBlockの終端で終わる。
  3. Tags/Anntations=要素のメタ情報。新しい行の@から始まる。

具体例。

<?php
/**
 * A summary informing the user what the associated element does.
 *
 * A *description*, that can span multiple lines, to go _in-depth_ into
 * the details of this element and to provide some background information
 * or textual references.
 *
 * @param string $myArgument With a *description* of this argument,
 *                           these may also span multiple lines.
 *
 * @return void
 */
 function myFunction($myArgument)
 {
 }

以下の要素に前置できる。

  • require(_once)
  • include(_once)
  • class
  • interface
  • trait
  • function (including methods)
  • property
  • constant
  • variables, both local and global scope.

Inheritance

DocBlockはSummary/Descriptionを上書きしたり、拡張できる。@inheritdocを使う。

要素ごとに以下のタグを継承する。

Elements Inherited tags
Any @author, @version, @copyright
Classes and Interfaces @category, @package, @subpackage
Methods @param, @return, @throws
Properties @var

@subpackageタグは同じ@packageの親クラスのときだけ継承される。

よく使う@param/@returnの構文。

<type-expression          = 1*(array-of-type-expression|array-of-type|type ["|"])
array-of-type-expression = "(" type-expression ")[]"
array-of-type            = type "[]"
type                     = class-name|keyword
class-name               = 1*CHAR
keyword                  = "string"|"integer"|"int"|"boolean"|"bool"|"float"
                           |"double"|"object"|"mixed"|"array"|"resource"|"scalar"
                           |"void"|"null"|"callable"|"false"|"true"|"self"

クラス名以外は全小文字。

基本は @<directive> <Type> <name> <description> の書式。スペース区切り。

  • @property: クラスの注釈部で指定する。メンバー変数の説明。

PHPUnit

PHPUnitを使用すれば自動単体テスト (Unit test) が可能だ。

Version

情報源: Supported Versions of PHPUnit – The PHP Testing Framework

PHPUnitのバージョンごとに対応しているPHPのバージョンが決まっている。

PHP v7.4に対応してい最後のバージョンはPHPUnit 9なので、当分はこれを使うのが良い。

Basic

出典: 2. Writing Tests for PHPUnit — PHPUnit 9.6 Manual

基本的な使用方法を整理する。

  1. 基本的にはクラス単位で試験コードを記載。<Class>クラスの試験コードは<Class>Testの命名にする。
  2. <Class>Test はPHPUnit\Framwork\TestCaseを継承させる。
  3. 試験はpublicのtest*メソッドの命名にする。あるいは、@testのアノテーションを付ければ、命名規則に従わなくてもいい。
  4. test*メソッド内で、assertSame() などで、期待値との比較で試験を行う。

例:

<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class StackTest extends TestCase
{
    private static $dbh;
    private $instance;
    
    public static function setUpBeforeClass(): void
    {
       // DB接続などクラス全体の初期化処理
       self::$dbh = new PDO('');
    }
    public static function tearDownAfterClass(): void
    {
        self::$dbh = null;
    }
    protected function setUp(): void
    {
      // 該当インスタンスの生成などメソッド単位の初期化処理。
      $instance = new Stack();
    }
    public function testPushAndPop(): void
    {
        $stack = [];
        $this->assertSame(0, count($stack));

        array_push($stack, 'foo');
        $this->assertSame('foo', $stack[count($stack)-1]);
        $this->assertSame(1, count($stack));

        $this->assertSame('foo', array_pop($stack));
        $this->assertSame(0, count($stack));
    }
}

Depends

前回の試験で準備した結果を利用したい場合、@dependsのアノテーションでテスト関数を指定しておくと、指定したテスト関数のreturnを引数に受け継いだテスト関数を記述できる。

@dependsは複数指定でき、指定順に事前に試験が実行されて引数に渡される。

Data Provider

ある関数に対して、テストケースを用意して、複数の引数の組み合わせを試験したいことがよくある。こういうときのために、テスト関数に渡す引数を生成する関数のデータプロバイダーを指定できる。@dataProviderで関数を指定する。データプロバイダーは引数のリストを配列で返すようにする。

データ数が多い場合、名前付き配列にしておくと、どういうデータ項目で失敗したかがわかりやすい。

Iteratorオブジェクトを返してもいい。

Fixtures

出典: 4. Fixtures — PHPUnit 9.6 Manual

テストメソッドの実行前に、テスト対象のインスタンスの生成や、DB接続など準備がいろいろある。これをFixturesと呼んでいる。この準備がけっこう手間になる。これを省力できるのがテストフレームワークの利点。

テストメソッド実行前後に共通で行える処理がある。

  • setUp/tearDown: テストメソッド単位の前後処理。テスト対象インスタンスの生成など。tearDownは何もしなくてもいいことが多い。
  • setUpBeforeClass/tearDownAfterClass: クラス単位の前後処理。 DB接続など。

XML Configuration File

出典:

基本はコマンドでテスト対象クラス・ファイルを指定してテストを実行する。他に、XMLの設定ファイル (phpunit.xml) でもテスト対象を指定できる。

testsディレクトリーの全*Test.phpを対象にする最小限の例は以下。

<phpunit bootstrap="src/autoload.php">
  <testsuites>
    <testsuite name="money">
      <directory>tests</directory>
    </testsuite>
  </testsuites>
</phpunit>

以下のように--testsuiteで試験対象を指定して実行する。

phpunit --bootstrap src/autoload.php --testsuite money

Assertions

Ref:

基本はassertSameでテストすればいいのだが、それ以外にも例外とかいろいろ試験したいケースがあるので、メソッドを整理する。

Exception

特に例外の試験がイレギュラー。

<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class ExceptionTest extends TestCase
{
    public function testException(): void
    {
        $this->expectException(InvalidArgumentException::class);
        // Run test target code following.
    }
}

上記のようにexpectExceptionを使う。

  • expectException:
  • expectExceptionCode:
  • expectExceptionMessage:
  • expectExceptionMessageMatches:

例外が発生する処理の前に記述しておく。

Output

echoなど標準出力を試験する際も専用のメソッドがある。

  • void expectOutputRegex(string $regularExpression)
  • void expectOutputString(string $expectedString)
  • bool setOutputCallback(callable $callback)
  • string getActualOutput()

expectExceptionと同様に事前にセットしておく。

Command-Line

Ref: 3. The Command-Line Test Runner — PHPUnit 9.6 Manual.

phpunitコマンドでいろいろできる。いくつか重要なオプション、使用方法がある。

  • phpunit file.php: 指定したファイルのテストを実行。
  • --testsuite <name>: テストを指定。

Test Doubles

Ref: 8. Test Doubles — PHPUnit 9.6 Manual.

テスト時に、依存関係を模擬したもので置換したいことがある。PHPUnitにそういう仕組が用意されている。

stub=親、mock=子。

メソッド内で他のクラス・メソッドを使う場合はmockで対象クラスを模擬させる。

<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class SubjectTest extends TestCase
{
    public function testObserversAreUpdated(): void
    {
        // Create a mock for the Observer class,
        // only mock the update() method.
        $observer = $this->createMock(Observer::class);

        // Set up the expectation for the update() method
        // to be called only once and with the string 'something'
        // as its parameter.
        $observer->expects($this->once())
                 ->method('update')
                 ->with($this->equalTo('something'));

        // Create a Subject object and attach the mocked
        // Observer object to it.
        $subject = new Subject('My subject');
        $subject->attach($observer);

        // Call the doSomething() method on the $subject object
        // which we expect to call the mocked Observer object's
        // update() method with the string 'something'.
        $subject->doSomething();
    }
}

基本的な作り。

  1. createMock(<class>::class)で該当クラスのモックを作成。
  2. expectsに呼出回数条件のオブジェクトをセット。
  3. methodで対象メソッドを指定。
  4. withで、該当メソッドの引数処理を指定。

デフォルトで模擬実装はnullを返す。戻り値を変更したければ、will($this->returnValue())などを指定する。よく使うので短縮記法もある。

Table 8.1 Stubbing short hands
short hand longer syntax
willReturn($value) will($this->returnValue($value))
willReturnArgument($argumentIndex) will($this->returnArgument($argumentIndex))
willReturnCallback($callback) will($this->returnCallback($callback))
willReturnMap($valueMap) will($this->returnValueMap($valueMap))
willReturnOnConsecutiveCalls($value1, $value2) will($this->onConsecutiveCalls($value1, $value2))
willReturnSelf() will($this->returnSelf())
willThrowException($exception) will($this->throwException($exception))

willReturnCallbackで呼び出し関数をまるごと別のものに置換できる。これが非常に便利。

Topic

Test private/protected

Ref:

クラスのprivate/protectedメソッドのテストには工夫が必要となる。

    /**
     * privateメソッドを実行する.
     * @param string $methodName privateメソッドの名前
     * @param array $param privateメソッドに渡す引数
     * @return mixed 実行結果
     * @throws \ReflectionException 引数のクラスがない場合に発生.
     */
    private function doMethod(string $methodName, array $param)
    {
        // テスト対象のクラスをnewする.
        $controller = $this->instance;
        // ReflectionClassをテスト対象のクラスをもとに作る.
        $reflection = new \ReflectionClass($controller);
        // メソッドを取得する.
        $method = $reflection->getMethod($methodName);
        // アクセス許可をする.
        $method->setAccessible(true);
        // メソッドを実行して返却値をそのまま返す.
        return $method->invokeArgs($controller, $param);
    }

ReflectionClassを使って取得できる。上記の関数のFormController部分を試験対象のクラスに差し替えればOK。$this->instanceを指定しておけばそのまま流用できるか。getProperty/getValueでprivateプロパティーも取得可能。

Test header

Ref: unit testing - Test PHP headers with PHPUnit - Stack Overflow.

header関数を使用する場合、phpunitの標準出力と干渉して以下のエラーが出て試験できない。

Cannot modify header information - headers already sent by (output started at .../vendor/phpunit/phpunit/src/Util/Printer.php:138)

回避方法が2種類ある。

  1. @runInSeparateProcess
  2. phpunit --stderr

1個目のアノテーションをテストメソッドに指定すると別プロセスでの実行になる。ただ、プロセス生成は時間がかかるため、試験が多いと効率が悪い。

2個目のphpunitの出力を標準エラーに出力させる方法がシンプルで効率もいい。phpunit.xmlに stderr="true"を指定するとキー入力を省略できる。こちらで対応しよう。

Test exit

Ref:

header()後のexit()など、exit/dieを使用するコードがある。phpunit内でこれらがあると、テストも強制終了になる。

上記の別プロセスで実行していた場合、以下のエラーになる。

Test was run in child process and ended unexpectedly

対処方法がいくつかある。

  1. exitを使わないコードに変更。
  2. isTestのようなフラグを元コードに入れてテスト可否で分岐してexitを回避。
  3. execで外部プロセスで実行してexitCodeを試験。
  4. exit/die部分だけ別関数に抽出してmockで置換?

<https://notabug.org/gnusocialjp/gnusocial/src/main/actions/apiaccountregister.php> のclientErrorが内部でexitする。

このclientErrorをwillなどで置換すればよさそう?

Language Reference

Types

Introduction

Ref: PHP: Introduction - Manual.

PHPの変数は以下の型のいずれかの値となる。

  • null
  • bool
  • int
  • float (floating-point number)
  • string
  • array
  • object
  • callable
  • resource

C言語のようなlong/doubleのような精度ごとの型はない。

System

PHP: 型システム - Manual

組込型の他に、ユーザー定義の型、aliasなどいくつかの型がある。

基本型

言語に統合されていて、ユーザー定義で再現不能。

  • 組込
    • null
    • スカラー型: bool/int/float/string
    • array
    • object
    • resource
    • never
    • void
    • クラス内の相対型: self/parent/static
  • Value型: false/true
  • ユーザー定義型/クラス型
    • インターフェイス
    • クラス
    • 列挙型
  • callable
複合型

複数の基本型を組み合わせた型。交差型とunion型で作れる。

  • 交差型: 宣言した複数のクラス型をすべて満たす型。&で表現。T/U/Vの交差型はT&U&Vと書く。
  • union型: 複数の型を受け入れる型。|で表現。T/U/Vのunion型はT|U|Vと書く。交差型を含む場合、T|(X&U)と丸括弧で囲む必要がある。
alias

PHPはmixedとiterableの2個の型のエイリアスに対応している。

  • mixed=object|resource|array|string|float|int|bool|null: PHP 8.0.0で導入。mixedは型のトップ。他の全部の型はこの型の部分になる。
  • iterable=Traversable|array: PHP 7.1.0で導入。foreachで反復可能でジェネレーター内でyield from可能。

ただし、ユーザー定義のエイリアスは未対応。

Boolean

条件判定にかかってくるので非常に重要。

まずは、下記のfalseになるもの一覧を把握し、それ以外はすべてtrueになるということを把握しておく。

  • booleanのfalse
  • intの0
  • floatの0.0
  • stringの空文字列、"0"
  • 要素数0個のarray
  • null (未初期化変数含む)

stringの"0"と要素0のarrayがfalseになる点が重要。注意する。要素0のarrayは包含判定、検索などでよく使う。

stringの0ははまりどころ。stringは何がくるかわからないなら、strlenで文字数を見たほうが確実。

Strings

Ref: PHP: 文字列 - Manual.

非常に重要。

Literal

文字列リテラルとしては4の表現がある。

  • Single quote: '' 変数展開されない。
  • Double quote: "" 変数展開される。
  • Here document: <<<EOT 二重引用符扱いで変数展開される。
  • Nowdoc: <<<'EOT' 一重引用符扱いで変数展開されない。

引用符内で引用符'を使う場合はバックスラッシュ\でエスケープが必要。バックスラッシュ自体の指定は二重\\。

Here document/Nowdocは終端IDのインデントで行頭を識別しており、インデントに意味があるので注意する。

echo <<<END
      a
     b
    c
\n
END;
echo <<<'EOT'
My name is "$name". I am printing some $foo->foo.
Now, I am printing some {$foo->bar[1]}.
This should not print a capital 'A': \x41
EOT;
Parse

二重引用符とヒアドキュメントではエスケープシーケンスが解釈され、変数が展開される。

<?php
$juice = "apple";

echo "He drank some $juice juice." . PHP_EOL;

// 意図しない動作をします。"s" は、変数名として有効な文字です。よって、変数は $juices を参照しています。$juice ではありません。
echo "He drank some juice made of $juices." . PHP_EOL;

// 参照する変数名を波括弧で囲むことで、変数名の終端を明示的に指定しています。
echo "He drank some juice made of {$juice}s.";

// 
$juices = array("apple", "orange", "koolaid1" => "purple");
echo "He drank some $juices[0] juice.".PHP_EOL;
echo "He drank some $juices[1] juice.".PHP_EOL;
echo "He drank some $juices[koolaid1] juice.".PHP_EOL;

// 複雑な例1

// これが動作しない理由は、文字列の外で $foo[bar]
// が動作しない理由と同じです。
// PHP はまず最初に foo という名前の定数を探し、
// 見つからない場合はエラーをスローします。
// 定数が見つかった場合は、その値('foo' そのものではない)
// を配列のインデックスとして使います。
echo "This is wrong: {$arr[foo][3]}"; 

// 動作します。多次元配列を使用する際は、
// 文字列の中では必ず配列を波括弧で囲むようにします。
echo "This works: {$arr['foo'][3]}";

// 複雑な例。二重展開で変数になる場合だけ式が使える模様。
$var1=9;
echo "{${mb_strtolower('VAR1')}}"; // 9

?>

波括弧はなくてもいいが、文字列が連結するなどして変数名の終端を区別できない場合に必須になる。

特に重要な挙動は以下。

  • ""内だと、連想配列添字の引用符不能。
  • ${}内だと、連想配列添字の引用符必要。

複雑な形式は{$ ... }がセット。{$ } 部分で変数が式扱いになる。

さらに複雑なことができる。{${}}を指定すると、内側の波括弧内で、${}部分が変数評価になる場合にだけ式を指定できる。動きがトリッキーすぎる。フォーマット文字列的なことには使えない。バグのもとになりそうなので使用を控えたほうがよさそう。

Format

Ref:

PHPにはPythonのformatメソッド相当はない。が似たような目的の関数がある。

  • sprintf/vprintf
  • strtr

strtrは第2引数にold => newの置換のペアの配列を渡す。やることは同じようなものだけどちょっと違う。

vsprintfは置換対象が可変長引数ではなく配列なだけ。

sprintf

Ref: PHP: sprintf - Manual

今後何度も使う。

C言語のprintfといろいろ違うところがある。

<?php
$format = 'The %2$s contains %1$d monkeys.
           That\'s a nice %2$s full of %1$d monkeys.';
echo sprintf($format, $num, $location);

echo sprintf("%'.9d\n", 123); // ......123
echo sprintf("%'.09d\n", 123); // 000000123

?>

特徴的なのが`%数$指定子`で引数の番号を選べるところ。Pythonの`{数:指定子}`に似ている。

後は埋める文字を指定する際は'を前置。

文字列の切り出し

いくつか方法がある。

preg_matchの自由度が高い。速度を気にしなくていいならこれでいいと思われる。ただ、引数の配列に入ってくるのがいまいち。関数の戻り値でほしい。

strposとsubstrを組み合わせると端の文字列を切り出せる。

文字列置換
  • str_replace
  • substr_replace: 日本語不能。
  • preg_replace
  • explode/implode
substr_replace($text, '', -1);
substr_replace($text, '.', nb_strrpos('_'));

1文字などの置換ならmb_strrposとの組み合わせ。

		$query = $request->query();
		foreach ($query as $key => $value) {
			unset($query[$key]);
			$keys = explode('_', $key);
			$key = implode('_', array_slice($keys, 0, -1)) . '.' . $keys[count($keys)-1];
			$query[$key] = $value;
		}

explode/implodeが無難で確実。

startsWith/endsWith

PHP 8なら「PHP: str_starts_with - Manual/PHP: str_ends_with - Manual」がある。

PHP 8未満なら以下のようなコード。

function startsWith( $haystack, $needle ) {
     $length = strlen( $needle );
     return substr( $haystack, 0, $length ) === $needle;
}
function endsWith( $haystack, $needle ) {
    $length = strlen( $needle );
    if( !$length ) {
        return true;
    }
    return substr( $haystack, -$length ) === $needle;
}

mb_strlen/mb_substrでマルチバイト対応。

改行分割

explode('\n', $csv) のようなことをしたくなるが、改行が\nとは限らない。

$array = preg_split('/\R/u', $string);

上記がいい。\Rが\r \n \n\rなどにマッチ。uで入力がUTF-8の場合を考慮。例えば、「腰」がuをつけないと分割されてしまう。

trim

PHP: trim - Manual

文字列の両端のホワイトスペースを除去する。

Array

Create

配列の作成方法がいくつかある。

Read
末尾要素

【PHP】配列の最後(末尾)の要素を取得まとめ array_key_last, count, end関数 | ヒシキリュウ.com

  • array_key_last: PHP v7.3.0+ ($arr[array_key_last($arr)];)。
  • count: 昔ながら ($arr[count($arr) - 1];)。
  • end: 非推奨。
Append

配列要素の追加。

$arr[キー] = 値;
$arr[] = 値;

array_push($arr, 'a', 'b'); // array_pushだと一度に複数追加できる。
array_unshift($arr, 'a', 'b'); // 先頭に追加。
array_merge($arr, [0, 1]); // 配列同士の追加。
$arr = [...$arr, ...[0, 1]] // PHP7.4以上。...演算子。性能はarray_mergeのほうが高い。

基本は$arr[キー] $arr[]でいいだろう。

Remove

配列要素の削除方法がいくつかある。

  • unset($arr[$key]);
  • array_shift($arr): 先頭要素を削除。削除済み要素を返す。破壊的な処理。
  • array_pop($arr): 末尾要素を削除。削除済み要素を返す。破壊的な処理。
  • array_slice (PHP: array_slice - Manual): 先頭・末尾の要素を除去した要素を返す。
$ar = [0, 1, 2];
foreach($ar as $e) {
    echo $e;
    if ($e === 1) {
        array_shift($ar);    
    }
}

print_r($ar);
012Array
(
    [0] => 1
    [1] => 2
)

途中で削除しても、foreachは詰めたりしない。

Copy

How to clone an array of objects in PHP? - Stack Overflow

配列変数を代入すると通常はそれでコピーになる。ただし、配列にオブジェクトがあると、そのオブジェクトはシャローコピーになる。

$new = array();

foreach ($old as $k => $v) {
    $new[$k] = clone $v;
}

上記のように配列要素をcloneでコピーして作る必要がある模様 (PHP: オブジェクトのクローン作成 - Manual)。

Comma

PHPでは配列の終端カンマは許容される。

他にも、名前空間のグループ指定はPHP7.2以上、関数の引数はPHP7.3以上で可能になった。

連想配列判定

配列か連想配列か判定する #PHP - Qiita

<?php
if (array_values($arr) === $arr) {
  echo '$arrは配列';
} else {
  echo '$arrは連想配列';
}

これで添え字が、数字かどうかをみるのがいい模様。

Convert
連想配列→単純配列

associative arrayをsimple arrayに変換する。

Convert an associative array to a simple array of its values in php - Stack Overflow

連想配列をキーと値のペアの配列にするちょっと気のきいた方法(かも) #PHP - Qiita

  • $array = array_values($array);: 値だけを1次元にしたい場合。
  • array_map(null, array_keys($a1), array_values($a1));: 連想配列の[[key,value], [key2, valu2]] 形式。

後者のパターンはそれなりに使う気がする。

単純配列→連想配列

PHP で通常の配列を連想配列に変換する

いくつか方法がある。

  1. array_combine
  2. array_fill_keys
  3. foreach
  4. array_flip
$ar = ['a', 'b'];
$ar2 = array_combine($ar, $ar);
var_dump($ar2);

array_combineがシンプル。array_fill_keysは0初期化などしたい場合。

CSV→連想配列

CSVを、よくDBの取得結果の形式の、行単位連想配列に変換する。方法がいくつかある。

<?php 
$csv = array_map('str_getcsv', file($file));
if (count($csv) && !count($csv[count($csv)-1])) unset($csv[count($csv)-1]);
array_walk($csv, function(&$a) use ($csv) {$a = array_combine($csv[0], $a);}); 
array_shift($csv); # remove column header 
?>
$rows = array_map('str_getcsv', file('myfile.csv'));
$header = array_shift($rows);
$csv = array();
foreach ($rows as $row) {
  $csv[] = array_combine($header, $row);
}

1番目の方法がシンプル。

Search

【PHP入門】配列の値を検索するarray_searchと他4つの関数 | 侍エンジニアブログ

いくつか方法がある。

基本はin_array。

array_key_exists/キー確認

PHP: array_key_exists - Manual

配列のキーの存在確認のほうほうがいくつかある。

  • array_key_exits: array_key_exists('first', $search_array);
  • isset: nullだとfalseになる (isset($search_array['first']))。
  • empty: nullだとfalseになる。
  • ??: キー不在だとnullになるのでこれでない場合に対応できる。

基本はarray_key_exitsか??。$ar ?? nullでWARNINGを回避しながら手短にかける。

空確認

PHP 配列が空かどうかを判定する #初心者 - Qiita

emptyで確認できる。が、単にnullなどの場合も判定してしまう。null or emptyという意味ならemptyでもOK。

逆に、issetであることと、nullではないことを確認できる。

array_search

array_search() - 指定した値を配列で検索し、見つかった場合に対応する最初のキーを返す

全てのキーが必要なら、array_keysにfilter_valueを指定する。

in_array

in_array — 配列に値があるかチェックする

in_array(mixed $needle, array $haystack, bool $strict = false): bool

haystack 内の needle を検索します。 strict が設定されていない限りは型の比較は行いません。

基本は$strict=trueで指定したほうがいい。完全一致検索。

any/all/some/every

Is there a PHP equivalent of JavaScript's Array.prototype.some() function - Stack Overflow

配列に対する、1個または全部の評価。

JavaScriptのsome/every相当。

PHP 8.4ならarray_any/array_allが存在する。

PHP 8.4未満なら、いくつか方法がある。

function array_any(array $array, callable $fn) {
    foreach ($array as $value) {
        if($fn($value)) {
            return true;
        }
    }
    return false;
}

function array_every(array $array, callable $fn) {
    foreach ($array as $value) {
        if(!$fn($value)) {
            return false;
        }
    }
    return true;
}
function array_some(array $data, callable $callback) {
    $result = array_filter($data, $callback);
    return count($result) > 0;
}

$myarray = [2, 5, 8, 12, 4];
array_some($myarray, function($value) {
    return $value > 10;
}); // true

foreachで途中で終わるほうが速い模様。

配列同士の包含・交差判定

1個でも入っているかを見たければ、array_intersect (PHP: array_intersect - Manual) がこの目的に合致する。

$peopleContainsCriminal = !empty(array_intersect($people, $criminals));
$peopleContainsCriminal = array_intersect($people, $criminals);

$criminalsの配列に、$peopleの要素のいずれかが入っているかを上記で判断できる。

array_intersectは1個目の配列要素の内、2個目の存在要素を返す (交差)。交差があれば、1個はあるという意味で、any/someになる。

全部の包含判定したい場合、array_diff (PHP: array_diff - Manual) でできる。

$containsAllValues = !array_diff($search_this, $all);

array_diffはarray_intersectと異なり、1個目の配列要素の内、2個目の不在要素を返す (差分)。なので、空なら全包含となる。非空なら非全包含=some。

完全一致なら、===でOK。

ポイントとしては、1個目の要素は要素数が少ない配列を指定したほうが速くなる。判定だけで、速度が重要なら、foreachで見つかったらすぐreturnしたほうが速い。

array_intersectが実行結果とboolが同じ向きなので、これを使うとわかりやすいだろう。

Array Functions
compact

Ref: PHP: compact - Manual.

変数名とその値から、配列を作る。extractの逆。

MVCのViewに複数の値を渡す場合などによく使う。

extract

Ref: PHP: extract - Manual.

配列のキー・バリューを変数として取り込む。

array_map
array_map(?callable $callback, array $array, array ...$arrays): array

JavaScriptのmap相当。非常に重要でよく使う。

callbackにnullを指定すると、複数の配列のzip (unpack) を行う。

ただ、array_mapのコールバックの引数は通常配列の要素が想定されていて、連想配列のキーにはアクセスできない。

それをしたかったら、array_reduceを使う。らしい。

Howto use array_map on associative arrays to change values and keys - Daniel Auener

array_flip

PHP: array_flip - Manual

配列のキーと値を反転した配列を返す。元のarrayの値は有効なキーを必要とする。つまり、intかstring。型が違う場合、警告が出て無視される。

また、同じ値が複数ある場合、最後のみが有効になる。

Type declarations/型宣言

PHP: 型宣言 - Manual

関数の引数、戻り値、クラスのプロパティー (PHP 7.4.0以上) に型を宣言できる。これにより、型を保証でき、その型でなければ、TypeErrorをスローする。

関数の戻り値だけ、型の指定箇所がやや特殊で、それ以外は原則変数の直前。関数の戻り値の場合、(): の後に指定する。

function name(): type {}
<?php
function sum($a, $b): float {
    return $a + $b;
}

// float が返される点に注意
var_dump(sum(1, 2));
?>

nullable な型とシンタックスシュガー

nullableの場合、型名の前に?を指定する (PHP 7.1.0以上)。?TとT|nullは同じ意味。

単一の基本型を宣言した場合、 型の名前の前にクエスチョンマーク (?) を付けることで、nullable であるという印を付けることができます。 よって、?T と T|null は同じ意味です。

注意: この文法は、PHP 7.1.0 以降でサポートされており、 PHP 8.0で一般化された union 型がサポートされる前から存在します。

PHP 7.4未満などの場合は、しかたないのでアノテーションで対応する。

Type juggling

PHP: 型の相互変換 - Manual

型の相互変換。非常に重要。

いろいろ方法がある。

共通なのはキャスト (cast)。

<?php
$foo = 10;   // $foo は整数です
$bar = (bool) $foo;   // $bar は boolean です
$fst = "$foo"; // to string.
?>

C言語と同じで (型) を前置する。ただし、少々長い。

文字列への変換は二重引用符囲などもOK。まあ、キャストだけ覚えておくのがシンプル。

Other

型判定

PHP: gettype - Manual

PHPでの型確認・判定方法がいくつかある。

  • gettype: 変数の型を文字列で返す。boolean/integer/double/string/array/object/resouce/resource (closed) (PHP v7.2.0以上)/NULL/ unknown type
  • get_class: オブジェクトのクラス名
  • get_debug_type: 変数の型名をデバッグしやす形で取得。
  • is_型名: is_array/is_bool/is_callable/is_float/is_int/is_null/is_numeric/is_object/is_resoure/is_scalar/is_string/function_exists/method_exists

基本はis_型名だろう。

Variables

Basics

Ref: PHP: Basics - Manual.

Undefined variable

未定義変数 (undefined variable) の値はNULL。

未定義変数 (配列の不在キー) にアクセスすると、E_WARNING (PHP 8未満はE_NOTICE) レベルのエラーが生じて、nullを返す。回避したければ、isset()で検知する。要素の追加時のアクセスは問題ない。

未定義変数の検知・制御方法がいくつかある。

  • isset (PHP: isset - Manual)
  • empty (PHP: empty - Manual)
  • ??: Null 合体演算子/Null collapsing operator
  • ??=: NULL合体代入演算子 PHP v7.4以上。
  • @: エラー制御演算子
  • array_key_exists

issetとempty、Null合体演算子あたりをメインで使う。特にempty。

emptyは以下相当を実施してくれる。値そのものの評価もするので、値が0で正常なときなど場合によっては困る場合もある。

!(isset($var) && $var)
!isset($var) || $var == false

isset($var) && $varは頻繁に使うことになるだろうから、emptyで短縮できる。

empty($var) ? false : true;
$var ?? false;

emptyとissetは関係が逆に似ているがissetは挙動が違う。

「Returns true if var exists and has any value other than null. false otherwise.」なので、変数の値を評価はしない。nullかどうかだけしかみない。emptyとは扱いが違うので注意する。

だから、頻繁に使うだろう。emptyとNull合体演算子の上記の記法はいろんなところで頻繁に使うと思われる。基本重要構文。

ただし、emptyは配列が空の場合もtrueになるので、そこは注意する。配列変数の有無を見たければ、issetを使うしかない。

Null合体演算子はNULLしかカバーしないから、emptyが必要な場面がけっこうある。

emptyの反対は、strlen/countあたり。ただし、未定義変数のチェックをしてくれないので、!emptyしたほうがいい。

Variable scope

About

出典: PHP: Variable scope - Manual

関数の外で使用するとグローバルスコープになる。ただし、関数内では暗黙にはグローバル変数は使えない。未定義変数扱いになる。

関数内でグローバル変数を参照したければ、関数内でglobalで明示的に使用したいグローバル変数を宣言する必要がある。

<?php
$a = 1;
$b = 2;

function Sum() 
{
    global $a, $b;

    $b = $a + $b;
}

あるいは、$GLOBALS配列にグローバル変数が入っているのでこれを使う。

なお、波括弧のブロックスコープは存在しない。C系言語の感覚だと、波括弧でスコープが作られそうなイメージがあるが、PHPの波括弧はスコープを作らない。あくまで、関数の内部かどうか。

逆にいうと、関数内に定義される関数・クラスも基本グローバル。

子関数に変数を渡したい場合、引数かグローバル変数しかない。他に隠蔽したり、親関数からスコープを引き継ぎたい場合、無名関数を使うしか無い。

Super global

PHP: スーパーグローバル - Manual

全てのスコープで使用可能な組込変数。関数、メソッド内でもglobal $variable;とする必要がない。

  • $GLOBALS: グローバル変数の連想配列。
  • $_SERVER
  • $_GET
  • $_POST
  • $_FILES
  • $_COOKIE
  • $_SESSION
  • $_REQUEST
  • $_ENV

Variables From External Sources

Ref:

PHPとHTMLフォームの関係がある。重要。

配列渡しはPHP側の仕様。

Constants

About

定数は値のためのID (名前)。基本的にスクリプト実行中に変更できない。大文字小文字を区別するが、慣習として大文字で表記する。

constキーワードか、define関数で定義できる。constの場合、制約がある。

constで指定可能なのは、スカラー式 (bool/int/float/string) と、スカラー式のみのarray。動的な設定はできない。

変数と異なり、$の前置は不要。

定数の定義判定は、defined()を使う。

定数の変数との違いは以下。

  • $不要。
  • スコープに関係なく、あらゆる場所からアクセス可能。
  • 後から再定義、未定義不能。
  • スカラー値と配列のみ。

constはコンパイル時に定義されるため、トップレベル以外、つまりブロック内部 (関数/ループ/if/try) で宣言できない。defineはできる。

define/const

項目 const define
構文 予約語 (少し速い) 関数
戻り値 なし あり
定義元 スカラー値のみ 変数/関数OK
クラス定数 x -
使用箇所 制御ブロック内部以外 どこでも
スコープ 名前空間 グローバル

defineはブロック内で使えるので、何らかの条件で定義を変更できるのが利点。例えば、環境を本番とデバッグに変えたりなど。

動的に変更したいならdefine、それ以外は名前空間やクラス定数として使えるconstだろうか。関数内のマジックナンバー的な使い方はできない。そういうのは、普通の変数で取り扱う。

ただ、constはアプリの設定として使うことはない。クラスの固有値の定義。

constant

PHP: constant - Manual

定数名の文字列で、定数の値を取得したい場合に使える。

定数の他に、enumのcaseにも使える。

class

クラス内に定数を定義できる。デフォルトでpublic。staic変数的な扱い。インスタンスではなく、クラスが保有する。

predefined

言語で定義済みの定数がいろいろある。true/false/nullなど。

Magic/マジック定数

使用箇所で値が変化する定数 (マジック定数) が9個ある。C言語のマクロに近い。コンパイル時に解決される。大文字小文字を区別しない。

PHP の "マジック" 定数
名前 説明
__LINE__ ファイル上の現在の行番号。
__FILE__ ファイルのフルパスとファイル名 (シンボリックリンクを解決した後のもの)。 インクルードされるファイルの中で使用された場合、インクルードされるファイルの名前が返されます。
__DIR__ そのファイルの存在するディレクトリ。include の中で使用すると、 インクルードされるファイルの存在するディレクトリを返します。 つまり、これは dirname(__FILE__) と同じ意味です。 ルートディレクトリである場合を除き、ディレクトリ名の末尾にスラッシュはつきません。
__FUNCTION__ 関数名。無名関数の場合は、{closure}
__CLASS__ クラス名。 クラス名には、そのクラスが宣言されている名前空間も含みます (例 Foo\Bar)。 トレイトのメソッド内で __CLASS__ を使うと、 そのトレイトを use しているクラスの名前を返します。
__TRAIT__ トレイト名。 トレイト名には、宣言された名前空間も含みます (例 Foo\Bar)。
__METHOD__ クラスのメソッド名。
__NAMESPACE__ 現在の名前空間の名前。
ClassName::class 完全に修飾されたクラス名。

どれもよく使う。

Operators

PHP: Operators - Manual

Assignment/代入演算子

??=

    // NULL合体代入演算子
    $id ??= getId();

    // これと同じ
    $id = $id ?? getId();
    $id = @$id ?: getId();
    $id = isset($id) ? $id : getId();

NULL合体演算子の代入版。nullの場合の代入が簡単になった。PHP 7.4から使用可能。

Comparison/比較演算子

三項演算子 (条件演算子)

if/elseの短縮表記。デフォルト値の設定などでよく使う。

<?php
// 三項演算子の使用例
$action = (empty($_POST['action'])) ? 'default' : $_POST['action'];

// 上記は以下の if/else 式と同じです。
if (empty($_POST['action'])) {
  $action = 'default';
} else {
  $action = $_POST['action'];
}
?>

PHP特有事項として、真ん中を省略できる。その場合、1個目がtrueならそれがそのまま戻る。JavaScriptとかC系言語でも真ん中は省略できない。

expr1 ?: expr3 の結果は、expr1 が true と同等の場合は expr1、 それ以外の場合は expr3 となります。 この場合、expr1 は一度だけ評価されます。

条件演算子のネストはわかりにくいので推奨されない。が、条件演算子の省略形は安定している。false以外の最初の引数を評価する。

<?php
echo 0 ?: 1 ?: 2 ?: 3, PHP_EOL; //1
echo 0 ?: 0 ?: 2 ?: 3, PHP_EOL; //2
echo 0 ?: 0 ?: 0 ?: 3, PHP_EOL; //3
echo $undefinedVariable ?? false ?: 'false default';
?>

NULL合体演算子はnullの時のデフォルト値になるが、こちらはfalseの場合のデフォルト値設定。意味が違う。未定義変数アクセスをガードできないが、それ以外であれば条件演算子の短縮表記のほうをよく使う。非常に重要。

Null合体演算子を組み合わせて、未定義のなどの場合のデフォルト値設定で役立つ。

PHP: 論理演算子 - Manual

elvis演算子と呼ばれることもある模様。

It also combines nicely with the ?? operator, which is equivalent to an empty() check (both isset() and `!= false`):

$x->y ?? null ?: 'fallback';

instead of:

empty($x->y) ? $x->y : 'fallback'
Null 合体演算子/Null collapsing operator
<?php
// $_GET['user'] を取得します。もし存在しない場合は
// 'nobody' を用います。
$username = $_GET['user'] ?? 'nobody';
// 上のコードは、次のコードと同じ意味です。
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

$var ?? 'value = isset($var) ? $var : 'value';

変数がnullの場合のガードの簡易記法。PHP v7.0.0で追加。非常に便利。

Error Control/エラー制御演算子@

式の直前に@を前置すると、その式のエラーメッセージを無視する。

基本的に使わないほうがいい。if文などでガードするより処理が遅い。ただし、Viewなどであまり影響ない場合などは記述がシンプルになるという利点もあるかも。

ただ、やっぱり基本は使わないほうがいい。バグの見落としになる。

Logical/論理演算子

PHP: 論理演算子 - Manual

論理積と論理和が、and/orと&&/||で2種類存在する。演算子の優先順位が違う。

// $g に代入されるのは、(true && false) の評価結果です
// これは、次の式と同様です: ($g = (true && false))
$g = true && false;

// $h に true を代入してから "and" 演算子を評価します
// これは、次の式と同様です: (($h = true) and false)
$h = true and false;

なお、PHPの論理演算子は、常に論理値 (true/false) を返すので注意する。

$a = $var || 'default';

上記のように、デフォルト値の代入扱いでor演算子を使うことはできない。同じ論理型同士なら成立はするが。

デフォルト値扱いにしたければ、短縮条件演算子?:や、ヌル合体演算子??を使う。

...演算子/スプレッド演算子

関数と配列の2か所で意味がある。配列の他、Traversableオブジェクトも可能。

配列や配列変数の直前に...を前置する (スペースは任意)。

関数の場合、関数定義時の仮引数と、関数呼出時に使用可能。

関数定義時の仮引数で指定すると、その変数が可変長引数を受け入れることを意味する。型宣言はその左に指定可能。これにより、func_get_args()を使わなくてもよくなった。

関数呼出時に使用すると、引数を展開してくれる。配列のアンパックに近い。

<?php
function sum(...$numbers) {
    $acc = 0;
    foreach ($numbers as $n) {
        $acc += $n;
    }
    return $acc;
}

echo sum(1, 2, 3, 4);
?>
<?php
function add($a, $b) {
    return $a + $b;
}

echo add(...[1, 2])."\n";

$a = [1, 2];
echo add(...$a);
?>
<?php
function total_intervals($unit, DateInterval ...$intervals) {
    $time = 0;
    foreach ($intervals as $interval) {
        $time += $interval->$unit;
    }
    return $time;
}

【PHP8.1】あなたはどっち? array_merge VS unpacking(スプレッド演算子) #PHP - Qiita

なお、配列のアンパックに関しては、array_mergeのほうが速くてメモリーも少ないとのこと。

Control Structures

Source: PHP: Control Structures - Manual.

制御構造に関する別の構文

PHP: 制御構造に関する別の構文 - Manual

if、 while、for、 foreach、switch に関する別の構文がある。開き波括弧部分を:に、閉じ波括弧部分をendif;,endwhile;, endfor;,endforeach;, endswitch;などにできる。else:とelseif:に注意。

この構文は存在だけ知っておくだけでいいと思われる。

elseif/else if

PHP: elseif/else if - Manual

1単語で書ける。結果は同じだが、文法的な意味が異なる。

for/foreach

foreachは配列の反復処理のための制御構造。

foreach(iterable_expression as $value)
foreach(iterable_expression as $key => $value)

$keyも使いたい場合、2番目の形式を使う。

ループ中に$valueの要素を直接変更したい場合、&をつけておく。

foreach(iterable_expression as &$value)

declare

Source: PHP: declare - Manual.

PHPUnitのサンプルコード (Getting Started with Version 9 of PHPUnit – The PHP Testing Framework) などで冒頭に以下の記述がある。

<?php declare(strict_types=1);

これの意味が分かっていなかったので整理する。

declare文 (construct) は、コードブロックの実行指令となる。以下の構文となる。

declare (<directive>)
  <statement>

<directive> はdeclareブロックの挙動を指示する。指定可能なものは以下3個だ。

  1. ticks
  2. encoding
  3. strict_types: =1の指定でPHPの暗黙の型変換を無効にする (ストリクトモード)。ただし、影響するのはスカラー型のみ。型が違う場合、TypeErrorの例外が発生する。

指令はファイルコンパイル時に処理されるので、リテラル値のみが使用可能で、変数や定数は使用不能。

declareブロックの <statement> は、<directive> の影響を受ける実行部だ。

declare文はグローバルスコープで使われる。登場以後のコードに影響する。ただし、他のファイルからincludeされても、親ファイルには影響しない。だから安心して使える。

型安全にするために、基本的にPHPファイルの冒頭にdeclare(strict_types=1);を書いておいたほうがよいだろう。

return

PHP: return - Manual

関数を終了させて、結果を呼び出し元に返すというのは他の言語同様の動きだが、いくつか注意すべき挙動・使用方法がある。

returnで引数を省略すると、戻り値はnullになる。

呼び出し方法、場所で挙動が変わる。

  • 関数/eval内: 即座に関数を終了し、引数を関数の値として返却。
  • グローバルスコープ: スクリプト自体を終了。
  • include/require内: 呼び出し元のファイルに制御を戻す。includeの場合、引数はincludeの戻り値になる。

return文は関数ではないので、引数の括弧は不要。紛らわしいのでないほうがいい。

include内で使えるというのがみそ。config.phpでreturnだけした設定一覧を記述しておいて、includeで変数に取り込むというのをよくやる。

require/include/require_once/include_once

Basic

includeは指定したファイルを読み込み評価する。絶対パスで指定しない場合、include_pathの設定を利用する。include_pathにもなければ現在ディレクトリーも探す。

絶対パス、相対パスの前置があると、include_pathは無視する。

ファイルが読み込まれると、ファイル内のコードは、includeが実行された行の変数スコープを継承する。つまり、呼び出し行で利用可能な全変数がファイル内でも使用可能。ファイル内で定義された関数やクラスはすべて、グローバルスコープになる。ただし、includeが関数定義内に配置されたら、コードは関数内で定義されているとみなす。

ファイルの読込時にはHTMLモードになる。そのため、ファイル内でPHPコードを実行するなら、<?php ?>で囲む必要がある。

includeに失敗したらFALSEを返し、E_WARNINGを発生させる。成功したら、戻り値は1。ただし、ファイル内でreturnを実行したら、その値を返す。

includeは特別な言語構造のため、引数に括弧は不要。結果を評価したいならば、全体を括弧で囲む。

// 動作します。
if ((include 'vars.php') == TRUE) {
    echo 'OK';
}
require/include

requireはincludeとほぼ同じ。違いは、失敗時にE_COMPILE_ERRORが発生して処理を中断する点。includeはE_WARNINGで処理は継続する。

使い分けとして、変数読込などで読み込めなくても処理を進めて問題ない場合に、include。

関数定義など、絶対必要なものはrequireなど。

_once

読込済みなら、再読込しない点がinclude/requireとの決定的な違い。関数の複数定義のエラーを回避できたりする。

読み込めたらtrueを返す。

config.php

includeとreturnの組み合わせのconfig.phpの設定ファイルをいろんなアプリで使われている。

<?php
return [
    'name' => 'hoge',
    'value' => 'fuga',
];
?>
<?php

// configファイルを変数に代入
$config = include __DIR__ . '/config.php';

// 呼び出し。
var_dump($config['name']);

?>

こういう形式。このreturnだけの文は、ほぼinclude前提。

編集対象のアプリの設定を、既存コードと分離する際に、いい方法。

config.phpをアプリ内で作りたい場合、「How to create Dynamically create config/custom.php config file」にあるように、var_exportを使うとよい。

// create the array as a php text string
$text = "<?php\n\nreturn " . var_export($myarray, true) . ";";
config class

config.phpをどう用意するかは議論がある。

include/returnではなくて、クラスのconst定数にするという。

  • クラスのconst定数
  • iniファイル/parse_ini_file

他に、configクラスを用意しておいて、シングルトンか、staticメソッドで参照する形。

どれくらいの頻度で参照するか次第。参照頻度が低いなら、getで毎回設定ファイルを読み込む。多いならstaticのクラス変数にもたせる。

Function

User defined

PHP: ユーザー定義関数 - Manual

関数は以下のような構文で定義する。

<?php
function foo($arg_1, $arg_2, /* ..., */ $arg_n)
{
    echo "関数の例\n";
    return $retval;
}
?>

関数内では、他の関数やクラス定義を含む、PHPのあらゆるコードを使用可能。関数内で関数を定義できないC言語とは異なる。

PHPでは、変数と異なり、関数やクラスは全てグローバルスコープ。関数内で定義した関数も外部から呼び出し可能。スコープが欲しければ、無名関数を使う。

また、関数のオーバーロードもできない。関数をunsetしたり、再定義も不能。

可変引数と、デフォルト引数もある。

Argument

配列同様に、PHP 8.0.0から、引数リストの最後のカンマが許容される。

Reference

引数はデフォルトで値渡しになる。値がコピーされて渡される。関数内部で引数自体を修正したい場合、リファレンス渡しにする。

関数定義で変数の前に&をつけると、リファレンス参照になる。

<?php
function add_some_extra(&$string)
{
    $string .= 'and something extra.';
}
$str = 'This is a string, ';
add_some_extra($str);
echo $str;    // 出力は 'This is a string, and something extra.' となります
?>
Default

関数定義時に、引数部分で変数に値を代入するようにして、デフォルト値を定義できる。引数が指定されなかった場合に使われる。なお、nullが渡された場合も、デフォルト値の代入はされないので注意する。

function makecoffee($type = "cappuccino")
{
    return "Making a cup of $type.\n";
}

デフォルト値には、定数を指定できる。PHP 8.1.0から、new ClassName記法でインスタンスも指定できる。

デフォルト引数は、デフォルト値のない引数の右側の必要がある。そうでない場合、省略できず、指定する意味がなくなく。

可変長引数

引数リストに...を含めることで、可変長の引数を受け取ることを示す。...を前置した変数に配列として入る。

<?php
function sum(...$numbers) {
    $acc = 0;
    foreach ($numbers as $n) {
        $acc += $n;
    }
    return $acc;
}

echo sum(1, 2, 3, 4);

...の前に型宣言も付与できるが、その場合配列要素が全部その型が必要になる。

名前付き引数

PHP 8.0.0から名前付き引数が導入された。引数の位置、順番ではなく、名前ベースで渡せる。これにより、デフォルト値を持つ引数をスキップできるし、引数の順番を意識しなくてよくなる。

引数の名前の後にコロン:をつけたものを値の前につけて指定する。引数の名前には予約語も使える。ただし、変数など動的には指定できない。

位置引数との混在もできる。その場合、名前付き引数は最後にする必要がある。

<?php
myFunction(paramName: $value);
array_foobar(array: $value);

PHP 8.1.0では、引数を...で展開した後に、名前付き引数も指定できる。ただし、展開済み引数の上書きはだめ。

function foo($a, $b, $c = 3, $d = 4) {
  return $a + $b + $c + $d;
}

var_dump(foo(...[1, 2], d: 40)); // 46
var_dump(foo(...['b' => 2, 'a' => 1], d: 40)); // 46

var_dump(foo(...[1, 2], b: 20)); // Fatal error. Named parameter $b overwrites previous argument

Return value

Ref: PHP: 戻り値 - Manual.

関数はreturn文で値を返せる。そこで処理を終了する。

returnを省略した場合、nullを返す。

Variable Functions/可変関数/Callable/コールバック

PHPで関数を引数で指定したり、変数として扱う仕組みがある。evalを使う必要はない。

可変関数は、関数を文字列で実行する仕組み。これとは別で、Callableという型がある。

可変関数は、関数名の文字列の変数に丸括弧を追加したら実行できるというもの。インスタンス変数があれば、メソッドもできる。

PHP 7.0から、関数のみ"str"()も可能になった。「PHP: PHP 5.6.x から PHP 7.0.x への移行 - Manual」に記載はないが、パース方法が変わったことが由来の模様。

関数もメソッドも統一的に扱うものとして、Callable型がある。

CallableはPHPで関数を引数として渡したり、関数名の文字列を渡して、動的に関数を実行する仕組み。

callable型で表す。関数だけでなく、メソッドやstaticメソッドも対応できる。方法が2種類ある。

  1. 関数: 関数名の文字列。
  2. メソッド: 配列で指定。0番目の要素に、インスakeタンスやオブジェクト。1番目の要素にメソッド名の文字列で指定する。
  3. staticメソッド: 配列で指定。0番目の要素に、クラス名を指定する。'ClassName::methodName' 形式でも指定可能。
anonymous/無名関数

callableの型。非常に重要。

// "use" がない場合
$example = function () {
    var_dump($message);
};
$example();

// $message を引き継ぎます
$example = function () use ($message) {
    var_dump($message);
};

useを指定した場合だけ、親のスコープから変数を引き継げる。変数は関数定義時の値。


Classes and Objects

The Basics

Ref: PHP: The Basics - Manual.

class

class内には変数 (プロパティー)、定数、関数 (メソッド) を含められる。

class内の関数などで、これらのプロパティー、メソッド類の参照時は、$this->経由で参照する必要がある。

C系言語であれば、$this->相当は省略できたが、PHPでは指定が必要なので注意する。

::class

<className>::classでクラス名の完全修飾子の文字列を取得できる。

例外の試験など、クラス名の情報が必要な時によくみかける。

PHP 8.0.0からオブジェクトに対しても::classを使用でき、元のクラス名を取得できる。その場合、get_class()と同じ。同じならPHP 7で使えないのでget_class()でいいか。

Property

Ref: PHP: プロパティ - Manual.

クラスのメンバー変数のことをプロパティー (property) とPHPでは呼んでいる。

クラス内で、1以上のキーワード (アクセス権、static、PHP 8.1.0以後のみreadonly) のあとに、オプション型宣言 (PHP 7.4以後、readonly以外) の後に変数宣言を続ける。

public $var1
static $var2
var $var3

staticなど、アクセス権を指定しない場合、publicとデフォルトでみなされる。なお、varキーワードを使う方法もある。これはPHP4までのプロパティーの宣言方法。PHP5以後はpublicと同じ意味になる (What does PHP keyword 'var' do? - Stack Overflow)。

宣言時に初期値を代入もできるが、初期値は定数のみ。関数類は使用不能。

以下のエラーが出る。

PHP Fatal error: Constant expression contains invalid operations in /ぼくのかんがえたさいきょうのクラス.php on line 5

関数類で動的に代入したい場合、__constructでやる。

Autoloading Classes

Ref:

別のファイルのクラスを使う方法の話。

  1. require_once()/require()/include: シンプルなファイル読み込み。PHP 4から。
  2. __autoload(): 非推奨。PHP 5.0で登場。
  3. spl_autoload_register(): PHP標準。PHP 5.1.0で登場。
  4. composer autoload: composer。

C系言語であれば、includeなどで外部ファイルをそのまま自分のファイルに読み込む。PHPでもrequire_onceなどで似たようなこともできる。が、PHPではこれをクラスごとに記述するのが煩雑だとして、自動で読み込む仕組みがいくつかある。

GNU socialでも <https://notabug.org/gnusocialjp/gnusocial/src/main/lib/util/framework.php> でspl_autoload_registerを使っている。

基本的にはcomposerのautoloadかPHP標準のspl_autoload_registerの2択になっている。

基本的な使用方法。

<?php
spl_autoload_register(function ($class_name) {
    include $class_name . '.php';
});

$obj  = new MyClass1();
$obj2 = new MyClass2(); 
?>

MyClass1.php MyClass2.phpから該当クラスを自動読み込みする。

該当クラスを使おうとしたときに、spl_autoload_registerに登録した関数が呼ばれる模様。

spl_autoload_registerは、指定した関数を__autoload()の実装として登録する。順番に登録する。

spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): bool

callback: callback(string $class): void 。重要。nullを指定するとデフォルトのspl_autload()が登録される。$classにはクラスの完全修飾子が入る。

このcallback内で独自のrequire_once相当をいろいろ指定する形になる。

スコープ定義演算子 (::)

スコープ定義演算子 (::) はトークンの一つ。定数、staticプロパティー、staticメソッド、親クラスなどにアクセスできる。

[Paamayim Nekudotayim] とも呼ぶ。ダブルコロンを意味するヘブライ語らしい。

staticメソッド/プロパティーは、遅延静的束縛 (Late Static Bindings) でアクセス可能。

  • MyClass::CONST_VALUE/$classname::CONST_VALUE;
  • self::$my_static
  • parent::CONST_VALUE
  • static: 実行時に最初の呼び出しクラスを参照。

staticは少々ややこしい。基本はself::でよいと思う。

Traits

PHP: トレイト - Manual

コード再利用のための仕組み。単一継承言語で、コードを再利用するための仕組み。関数クラス (デリゲート) 的なもの。クラスに関数クラスのメソッドを取り込める。インスタンス生成などはできず、関数を水平方向で構成可能にする。継承しなくても、メンバーに追加できる。

<?php
trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}

class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ... */
}

class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}
?>

Magic/マジックメソッド

PHP: マジックメソッド - Manual

PHPのデフォルトの動作を上書きする特別なメソッドをマジックメソッドと呼んでいる。

どらも__ (アンダーバー2個) から始まる。__始まりの全メソッドはPHPで予約されているのでユーザー定義メソッドとしては非推奨。

以下がある。

  • __construct
  • __destruct
  • __call
  • __callStatic
  • __get
  • __set
  • __isset
  • __unset
  • __sleep
  • __wakeup
  • __serialize
  • __unserialize
  • __toString
  • __invoke
  • __set_state
  • __clone
  • __debugInfo

__construct/__destruct/__clone以外の全マジックメソッドはpublic必須。E_WARNINGの警告が発生する。

__construct/__desctructは戻り値型を宣言してはいけない。

Other

クラス名の取得
get_class($object);
クラス名::class
$object::class // PHP 8.0以上 (get_class相当)
(new \ReflectionClass($obj))->getShortName();

基本は名前空間付きのフルパスでの取得。クラス名だけだとgetShortName()

Reserved

keywords

PHP: キーワードのリスト - Manual

式や関数ではなく、定数、クラス名、関数名として使えず、PHPで予約されている特別なキーワードがいくつかある。

statement/文に近い扱い。言語構文の一部扱い。

PHP のキーワード
__halt_compiler() abstract and array() as
break callable case catch class
clone const continue declare default
die() do echo else elseif
empty() enddeclare endfor endforeach endif
endswitch endwhile eval() exit() extends
final finally fn (PHP 7.4 以降) for foreach
function global goto if implements
include include_once instanceof insteadof interface
isset() list() match (PHP 8.0 以降) namespace new
or print private protected public
readonly (PHP 8.1.0 以降) * require require_once return static
switch throw trait try unset()
use var while xor yield
yield from

* readonly は、関数名として使用できます。

コンパイル時の定数
__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__
__NAMESPACE__ __TRAIT__

Interfaces

PHP: 定義済みのインターフェイスとクラス - Manual

stdClass

PHP: stdClass - Manual

動的なプロパティーが使える、汎用的な空クラス。このクラス自体は、メソッドやプロパティーを持たない。

json_decodeなど一部の関数がこのインスタンスを返す。

// 型変換での作成。連想配列を(object)にキャストすると作れる。
(object) array('foo' => 'bar');

データベースの取得結果が、連想配列の他に、stdClassになっていることがある。

匿名オブジェクトや、動的プロパティーなどが主な利用方法。

データホルダーとして使う場合、連想配列のキーのほうが、自由度が高いので、そちらのほうが便利だと思われる。たくさんある配列関数も使えるし。

Features

Ref: PHP: Features - Manual.

Handling file uploads

Ref: PHP: Handling file uploads - Manual.

input type="file"などのアップロードファイルのPHPでの処理方法・作法がある

<!-- データのエンコード方式である enctype は、必ず以下のようにしなければなりません -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
    <!-- MAX_FILE_SIZE は、必ず "file" input フィールドより前になければなりません -->
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    <!-- input 要素の name 属性の値が、$_FILES 配列のキーになります -->
    このファイルをアップロード: <input name="userfile" type="file" />
    <input type="submit" value="ファイルを送信" />
</form>

PHP側では$_FILES['userfile']に必要な情報が格納される。

  • $_FILES['userfile']['name']
    クライアントマシンの元のファイル名。
    $_FILES['userfile']['type']
    ファイルの MIME 型。ただし、ブラウザがこの情報を提供する場合。 例えば、"image/gif" のようになります。 この MIME 型は PHP 側ではチェックされません。そのため、 この値は信用できません。
    $_FILES['userfile']['size']
    アップロードされたファイルのバイト単位のサイズ。
    $_FILES['userfile']['tmp_name']
    アップロードされたファイルがサーバー上で保存されているテンポラ リファイルの名前。
    $_FILES['userfile']['error']
    このファイルアップロードに関する エラーコード
    $_FILES['userfile']['full_path']
    ブラウザからアップロードされたファイルのフルパス。 この値は実際のディレクトリ構造を反映しているとは必ずしも言えないため、 信用できません。 PHP 8.1.0 以降で利用可能です。

tmp_nameが非常に重要。これをリネームする形で保存する。あとはnameも保存時のファイル名で重要。

<?php
$uploaddir = '/var/www/uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);

echo '<pre>';
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
    echo "File is valid, and was successfully uploaded.\n";
} else {
    echo "Possible file upload attack!\n";
}

echo 'Here is some more debugging info:';
print_r($_FILES);

print "</pre>";

?>

上記がイメージ。

DBに保存する場合は「PHPとMySQLを利用した画像・動画のアップロード・保存・表示 #PHP - Qiita」も参考になる。

Using PHP from the command line

Ref: PHP: Command line usage - Manual.

簡単なコードの確認などでPHPをコマンドラインなどから簡単に実行したいことがよくある。いくつか方法がある (PHP: Usage - Manual)。

  1. phpコマンドの引数にファイルを指定: php file.php/php -f file.php
  2. phpコマンドの引数kにコードを指定: php -r 'print_r(get_defined_constants());'
  3. phpコマンドに標準入力で読み込み: php <file.php

標準入力が一番使いやすく感じる。

Function Reference

Affecting PHP's Behavior

Error Handling

PHP: エラー処理 - Manual

Runtime Configuration

PHPのエラー設定を整理する。 PHPのエラー設定は「PHP: Runtime Configuration - Manual」で一覧化されている。

xmlrpc_errors, syslog.facility, syslog.ident以外はどこでも設定可能。

特に重要なのが以下の設定。

設定 初期値 説明
error_reporting E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED エラー出力レベルを設定する。開発時にはE_ALL (2147483647/-1) にしておくとよい。
display_errors "1" エラーのHTML出力への表示を設定する。"stderr"を指定すると,stderrに送る。デフォルトで有効なのでそのままでいい。
display_startup_errors "0" PHPの起動シーケンス中のエラー表示を設定する。デバッグ時は有効にしておいたほうがいい。
log_errors "0" エラーメッセージのサーバーのエラーログまたはerror_logへの記録を指定する。これを指定しないとログが残らないため,常に指定したほうがいい
error_log NULL スクリプトエラーが記録されるファイル名を指定する。syslogが指定されると,ファイルではなくシステムロガーに送られる。Unixではsyslog(3)で,Windowsではイベントログになる。指定されていない場合,SAPIエラーロガーに送信される。ApacheのエラーログかCLIならstderrになる。

基本的に以下をphp.ini/.user.iniに設定しておけばよい。

error_reporting = E_ALL
display_startup_errors = on
log_errors = on

; For file
display_errors = stderr

htpd.conf/.htaccessの場合は以下。

php_value error_reporting -1
php_flag display_startup_errors on
php_flag log_errors on

# For file
php_value display_errors stderr
logrotate

出力したログファイルがストレージを圧迫しないように、一定サイズ・期間でリネームして、最大保持数を維持したりする。

いくつか方法がある。

  • GNU/Linuxのlogrotateコマンド
  • logrotateライブラリー
  • 自前実装

やることは決まっているのだから、自前で実装してもいいかもしれない。

  1. ログ出力時
  2. ログ出力ファイルのサイズを確認して、設定サイズより大きければ、循環。
  3. 最古のログファイルを削除して、順番にリネーム。
  4. 最後に出力。

それだけ。

const LOG_DIRECTORY = '/var/log/logdir/';           // ログディレクトリ
const LOG_FILENAME  = 'logfname.log';               // ログファイル名
const LOG_FILEPATH  = LOG_DIRECTORY.LOG_FILENAME;   // ログのファイルパス
const MAX_LOTATES   = 3;                            // ログファイルを残す世代数
const MAX_LOGSIZE   = 1024*1024;                    // 1ファイルの最大ログサイズ(バイト)

function WriteLog($strlog){

    // 保存先ディレクトリを作成
    if(!file_exists(LOG_DIRECTORY)){
        mkdir(LOG_DIRECTORY);
    }

    // ログのローテート
    if(@filesize(LOG_FILEPATH) > MAX_LOGSIZE){

        // 最古のログを削除
        @unlink(LOG_FILEPATH.strval(MAX_LOTATES));

        // ログをリネーム .log → .log_01
        for ($i = MAX_LOTATES - 1; $i >= 0; $i--) {
            $bufilename = ($i == 0) ? LOG_FILEPATH : LOG_FILEPATH.strval($i);
            @rename($bufilename, LOG_FILEPATH.strval($i+1));
        }
    }

    // ログ出力 
    file_put_contents(LOG_FILEPATH, date('y-m/d-H:i:s ').$strlog."\n", FILE_APPEND | LOCK_EX);
}

もう少しいい実装方法はありそう。

Database

PDO

Introduction

PHP Data Objects。PHPからDBへのアクセスの軽量で高性能なインターフェイス。DBの全関数を実行できるわけではない。DBアクセスの抽象化レイヤーを提供する。つまり、DBが何であろうが、同じ関数でSQLの発行、データ取得ができる。

Connections
Open

PDOインスタンスの作成で接続ができる。

<?php
try {
    $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
} catch (PDOException $e) {
    // たとえば、タイムアウトしたあとに再接続を試みます
}
?>

引数にDSNと、ユーザー名、パスワードを指定する。

PDOインスタンスが存在する間、接続が継続する。

PHP: PDO::setAttribute - Manual

オプションを設定できる。

Close

接続終了時には、明示的にオブジェクトを破棄する必要がある。具体的には、変数にnullを代入する。

<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
// ここで接続を使用します
$sth = $dbh->query('SELECT * FROM foo');

// 使用を終了したので、閉じます
$sth = null;
$dbh = null;
?>

nullにしない場合、script終了時に閉じられる。

ただ、通常はすぐに単体のscriptは終了するので、いちいち書く必要はない。

Query

SQLを実行するためのPDOメソッドがいくつかある。

  • PHP: PDO::exec - Manual: 引数のSQLを実行して、影響のある行数を返す。結果のいらないSQLやSELECT以外のINSERT/UPDATEなどで使う。
  • PHP: PDO::query - Manual: 引数のSQLを実行して、実行結果をPDOStatementで取得する。ユーザー入力を伴わない固定SQLで使う。似たようなSQLをループなどで複数回実行するならprepareのほうが性能が向上する。
  • PHP: PDO::prepare - Manual: PHP: PDOStatement::execute - Manual用のSQL文を用意する。プレースホルダーを用意する。プリペアードステートメントというSQLの機能を使う。ユーザー入力をエスケープしたり、一部分だけ異なるようなSQLがキャッシュされて効率が上がる。
prepared statement

PHP: プリペアドステートメントおよびストアドプロシージャ - Manual

重要。パラメーターマーク/プレースホルダーを配置したSQL文を用意して、後でプレースホルダーに変数をバインドしてSQLを実行する。

プレースホルダーとして、名前付きパラメーターと疑問符パラメーターの2種類がある。

  • 名前付きパラメーター: [:name] の形式で配置する。バインド時は名前。数が多い場合。バインド時は先頭の:は省略可能。compact関数で短縮できる。
  • 疑問符パラメーター: [?] を配置する。バインド時は0開始の番号。数が少ない場合シンプル。PHP 7.4.0以上で??で?自体をエスケープ。

なお、名前付きと疑問符は混在できない。プレースホルダーには、SQLのデータリテラルのみを配置できる。SQLの文などはだめ。

プレースホルダーはデータリテラル全体に適用が必要。つまり、LIKEの%は値に含める必要がある。代わりに、引用符がいらない。

<?php
/* 値の配列を渡してプリペアドステートメントを実行する */
$sql = 'SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour';
$sth = $dbh->prepare($sql, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
$sth->execute(['calories' => 150, 'colour' => 'red']);
$red = $sth->fetchAll();
/* 配列のキーの前にも、コロン ":" を付けることができます(オプション) */
$sth->execute([':calories' => 175, ':colour' => 'yellow']);
$yellow = $sth->fetchAll();
?>
<?php
/* 値の配列を渡してプリペアドステートメントを実行する */
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < ? AND colour = ?');
$sth->execute([150, 'red']);
$red = $sth->fetchAll();
$sth->execute([175, 'yellow']);
$yellow = $sth->fetchAll();
?>
$stmt->execute([1 => $gender, 0 => $city]);

疑問符の場合、キーを指定すれば、順番を変更してもOK。

PDOStatement::execute

PHP: PDOStatement::execute - Manual

プリペアードステートメントを実行する。実行にあたって、プレースホルダーに値を埋め込む必要がある。

  1. bindValue/bindParamを使用。
  2. 引数で配列で指定。ただし、NULL以外、値は全てPDO::PARAM_STR扱い。既存のbindValue/bindParamを全上書き。

型が全部PARAM_STRなら問題ない。それ以外の数値などを含めたいなら、bindValueでしたほうがいい。

PDOStatement::bindValue/bindParam

プリペアードステートメント内で、部分的に後からプレースホルダーに値を割り当てる。

  • bindValue: 呼び出し時に値で埋め込まれる。
  • bindParam: execute実行時に変数が評価される。

基本はbindValueでよい。が、bindParamも出番がある。

<?php
$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (?, ?)");
$stmt->bindParam(1, $name);
$stmt->bindParam(2, $value);

// 行を挿入します
$name = 'one';
$value = 1;
$stmt->execute();

// パラメータを変更し、別の行を挿入します
$name = 'two';
$value = 2;
$stmt->execute();
?>

変数だけ変えて、複数回実行する場合。

ただ、この場合、executeの引数で渡してもいい。が、引数だと全部PDO::PARAM_STRになってしまうので、bindParamも役立つ。

PDOで複数回SQLを実行: コツコツ学ぶWordPress、技術メモ

<?php
require("db_info.php");
$dsn = 'mysql:host=localhost;dbname='.$database.';charset=utf8';

try {
  $dbh = new PDO($dsn, $username, $password,
    array(
      PDO::ATTR_EMULATE_PREPARES =>false,
      PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"
    )
  );
} catch (PDOException $e) {
  exit('データベース接続失敗。'.$e->getMessage());
}

date_default_timezone_set('Asia/Tokyo');
$result = array();

$st = $dbh->prepare('select city,TRUNCATE(sum(setai)/3,0) as setai,TRUNCATE(sum(man)/3,0) as man,TRUNCATE(sum(woman)/3,0) as woman,TRUNCATE(sum(total)/3,0) as total from jinko where ym in (:ym1,:ym2,:ym3) group by city');

$ym1 = date('Y/m', strtotime(date('Y-m-1').' -1 month'));
$ym2 = date('Y/m', strtotime(date('Y-m-1').' -2 month'));
$ym3 = date('Y/m', strtotime(date('Y-m-1').' -3 month'));
$st->bindValue(':ym1', $ym1 , PDO::PARAM_STR);
$st->bindValue(':ym2', $ym2 , PDO::PARAM_STR);
$st->bindValue(':ym3', $ym3 , PDO::PARAM_STR);
$st->execute();
$result = $st->fetchAll(PDO::FETCH_ASSOC);

$ym1 = date('Y/m', strtotime(date('Y-m-1').' -1 year -1 month'));
$ym2 = date('Y/m', strtotime(date('Y-m-1').' -1 year -2 month'));
$ym3 = date('Y/m', strtotime(date('Y-m-1').' -1 year -3 month'));
$st->bindValue(':ym1', $ym1 , PDO::PARAM_STR);
$st->bindValue(':ym2', $ym2 , PDO::PARAM_STR);
$st->bindValue(':ym3', $ym3 , PDO::PARAM_STR);
$st->execute();
$result = array_merge($result,$st->fetchAll(PDO::FETCH_ASSOC));

echo json_xencode($result);

?>

ただ、bindValueをまたやってもいい。だったら、bindParamは余計になくてもいいか?

Fetch

PDOStatementからデータを取得するメソッドが複数ある。

FETCH_MODE

PDOのコンストラクターのオプション、setAttribute、fetchの引数で取得条件を指定できる。

  • PDO::FETCH_BOTH: 規定。カラム名と0開始の添え字の配列で返す。
  • PDO::FETCH_NAMED: 同名のカラムが複数ある場合、値の配列を返す。
  • PDO::FETCH_NUM: 0開始のカラム番号の配列で返す。

同名カラムがある場合、PDO::FETCH_NUM/FETCH_NAMEDじゃないと取れない。

Calendar

PHP: 日付および時刻関連 - Manual

time/速度計測

PHPで処理速度などを計測したいことがある。基本は処理前後のタイムスタンプの差分で、どの言語でも共通の論理だが、いくつか方法がある。

microtimeとhrtimeの2個の関数をタイムスタンプの取得で使える。hrtimeはPHP 7.3.0以上で使用可能。単位ナノ秒。問題なければ、こちらが推奨されている。HRTime (High Resolution Time) の拡張のモジュールと関係する関数とのこと。

両方とも、引数にtrueを指定して、floatで取得するのが基本。

<?php
$start = hrtime(true); // 計測開始時間

// 計測したい処理

$end = hrtime(true); // 計測終了時間

// 終了時間から開始時間を引くと処理時間になる
echo '処理時間:'.($end - $start).'ナノ秒' . PHP_EOL; 
?>

PHPのバージョンを気にするのが嫌なので、ラップするとよい。

<?php
/**
 * Time target function.
 * @param callable $callback Target function.
 * @return int|float Run time [ns].
 */
function timeit(callable $callback)
{
    $time = 'microtime';
    $nanoFactor = 1000;
    if (function_exists('hrtime')) {
        $time = 'hrtime';
        $nanoFactor = 1;
    }

    $start = $time(true);
    $callback();
    $stop = $time(true);
    return ($stop - $start) * $nanoFactor;
}

echo timeit(function(){sleep(1)});

// one liner.
(function($c){$s=hrtime(true);$c();return hrtime(true)-$s;})(function(){});

(function($c){$s=hrtime(true);$c();return hrtime(true)-$s;})(function(){sleep(1)});
?>

こんな感じ。

File system

Ref: PHP: ファイルシステム - Manual.

File system

file
  • file_get_contents — ファイルの内容を全て文字列に読み込む
  • file_put_contents: データをファイルに書き込む。戻り値に書き込みバイト数を返す。失敗したらfalse。成否は完全一致===falseで。
  • PHP: file - Manual: ファイル全体を読み込んで改行区切りで配列にする。

上記2個の非常に重要な入出力関数がある。

バイナリーやHTTP GETに対応している。アップロードされたファイルの読み込みなどでお世話になる。

file_get_contents/file_put_contentsはfopen/fwrite/fcloseの一連のファイル処理を含んでいるので非常に簡単。

directory
check

入出力とセットで使うファイルの不在確認の関数群。

path

パス関係の操作。重要。

Directory

Constants

PHP: 定義済み定数 - Manual

  • DIRECTORY_SEPARATOR: Windowsなら\、それ以外/らしい。

このDIRECTORY_SEPARATORは基本的には使うことはない。

理由として、Windowsがパスの文脈で/も受け入れるから。必要な場面は、OSからパスを受け取る場合に、\が返ってくることがあり、そこでexplodeしたりしたい場合のみ。

自分からパスを指定する文脈では、/で問題なく、DIRECTORY_SEPARATORを使う必要はない。

ディレクトリー以下のファイル一覧

方法がいくつかある。

  • glob
  • scandir
  • SPL

ソートや簡易検索が必要かどうかで、速度や適切な方法が異なる。

ただ、使いやすいのはglob。

glob(string $pattern, int $flags = 0): array|false

マッチする・ファイル、ディレクトリーの配列を返す。マッチしなければ空の配列。失敗はfalse。

patternはlibcのglobのルールでマッチする。

  • *: 0以上の任意文字。
  • ?: 任意の1文字。
  • [...]: グループの1文字。先頭が!の場合、否定。
  • \: エスケープ。

flags。特に重要なのは以下。

  • GLOB_ONLYDIR: ディレクトリーのみ。
  • GLOB_NOSORT: デフォルトでアルファベット順でソートしているのを無効にする。これを指定すると速くなる。

./..以外の全ファイルのマッチ。

array_merge(glob('.[!.]*'), glob('*'));

International

mbstring

mb_substr

Ref:

mb_substr(

    string $string,

    int $start,

    ?int $length = null,

    ?string $encoding = null

): string

substr同様、lengthにはマイナス値を指定可能。その場合、末尾からの文字数になる。

省略するかnullを指定すると、全文字。0は0文字。

Text

Strings

strpos

Ref: PHP: strpos - Manual.

strpos(string $haystack, string $needle, int $offset = 0): int|false

文字列 haystack の中で、 needle が最初に現れる位置を探します。

include相当。よく使う。

戻り値に注意。有無の確認時は、strpos() !== falseの厳密一致でチェックする必要がある。

echo/print/printf

PHPの出力関数群。よく使うが、扱いが特殊なので整理する。

まず、echo/printは関数ではなく、if/forなどと同じ言語構造、キーワード扱い。丸括弧はなくてもいい。紛らわしいのでないほうがいい。

  • 共通: 末尾に改行は付与されない。自分で"\n"を指定必要。関数ではないので丸括弧は不要。
  • echo: 戻り値void。式ではないので、if returnなどで使えない。戻り値がない分printよりわずかに速い。文字数が短い。コンマ区切りで複数列挙可能。文字列連結するよりコンマ区切りのほうが.演算子の優先順位など扱いが簡単。HTMLでの埋め込みに便利な <?= ?>の短縮表記 (<?php echo ; ?>相当)もあり。
  • print: 戻り値intで常に1を返す。ifや条件演算子の結果部分など、式の文脈で使用可能。

基本はechoでいい。if return/条件演算子など戻り値や式が必要な箇所でだけprintを使う。

printfは関数。書式指定が必要ならこれ。

Basic/Vartype/変数・データ型関連

Variable

print_r/var_export/var_dump

PHPの変数の内容を、配列やオブジェクトなどの複雑なデータ型でも表示、出力可能なのがprint_r/var_export/var_dump。微妙に違いがある。

  • var_dump: これのみ型情報がある。
  • var_export: 変数の文字列表現。config.phpなどファイルに出力すればそのままinclude可能。
  • print_r: 基本は閲覧用の文字列。
  • print_r/var_export: 第二引数をtrueにすると、画面出力ではなく、戻り値に返す。
$text = "<?php\n\nreturn " . var_export($myarray, true) . ";";

使うとしたら、var_exportとvar_dumpだと思われる。

var_dumpは型の情報が詳しい。詳細な情報が欲しい場合、var_dump。そうでない、単に見たいだけなら、var_exportで十分。

var_dumpは表示のみ。出力制御関数を使えば、文字列に保存はできるが、基本はデバッグ表示用。

<?php
$data = array(
    "A" => "Apple",
    "B" => "Banana",
    "C" => "Cherry"
);

echo "---print_r---\n";
print_r($data);

echo "---var_export---\n";
var_export($data);

echo "---var_dump---\n";
var_dump($data);
?>
---print_r---
Array
(
    [A] => Apple
    [B] => Banana
    [C] => Cherry
)

---var_export---
array (
  'A' => 'Apple',
  'B' => 'Banana',
  'C' => 'Cherry',
)


---var_dump---
array(3) {
  ["A"]=>
  string(5) "Apple"
  ["B"]=>
  string(6) "Banana"
  ["C"]=>
  string(6) "Cherry"
}

画面出力でデバッグしたいならば、前後でpre要素を出力させる。

<?php
function print_r2($val){
        echo '<pre>';
        print_r($val);
        echo  '</pre>';
}
?>
var_exportの変数取込

var_exportの文字列表現。config.phpに出力して、Includeするほかに、テキストを変数にしたいことがある。

素直にevalする。

$dumpStr = var_export($var,true);
eval("$somevar = $dumpStr;");

くれぐれも入力に注意する。

Process

exec

システムプログラムの実行のための関数群がある。Windowsだとcmd.exe経由で実行される。

exec/shell_exec/system/passthru

外部プログラム実行のための3の関数がある。違いがある。

  • shell_exec: 標準出力をstringで返す。バッククオート演算子``と同じ。プログラムの成功可否、終了コードは判断不能。
  • exec: 既定で標準出力の最後の1行のみ返す。第二引数に配列を指定すれば、行区切りの配列で返すこともできる。
  • passthru: Unixコマンドの出力がバイナリーで、ブラウザーに直接バイナリーを返す場合に、exec/systemの代わりに使う。戻り値は?falseで標準出力に直接結果を出力する。
  • system: C言語のsystem関数に類似。system()でコマンドを実行して出力する。成功時にコマンド出力の最終行を返す。出力をファイルや別のストリームにリダイレクトしないと、終了までPHPが止まる。
項目 exec passthru shell_exec/`` system
出力 - x - x
終了コード x x - x
戻り値 最終行 null/false 全行 最終行
全行取得 x - x -
用途 外部コマンドの結果文字列取得 (終了チェックあり) 画像応答 外部コマンドの結果文字列取得 文字応答

基本はshell_exec/`` execで十分。

exec

PHP: exec - Manual

exec(string $command, array &$output = null, int &$result_code = null): string|false

戻り値は最終行。失敗したらfalseを返す。実行コマンドの終了コードは$result_codeに渡される。

escapeshellarg/escapeshellcmd

exec/shell_exec/``と併用するエスケープ用関数。

  • escapeshellarg: 引数の文字列を一重引用符で囲み、既存の一重引用符を苦オートする。これで、引数全体を1個の引数にする。複数の引数の誤り実行を回避できる。
  • escapeshellcmd: シェルに特殊な意味のある&#;`|*?~<>^()[]{}$\、\x0A のシェルの特殊文字にバックスラッシュを追加し、'"は対がない場合のみエスケープ。

元々、コマンド全体をエスケープする [escapeshellcmd] だけがあった。が、これだとコマンドの引数を追加する攻撃が可能になるので、 [escapeshellarg] が追加されたらしい (PHPのescapeshellcmdを巡る冒険 | 徳丸浩の日記)。

ただ、escapeshellcmdは、パラメーターインジェクションの危険性があるので、使ってはいけないらしい。

基本は引数に [escapeshellarg] を使うだけ。

Other/その他の基本モジュール

PHP: その他の基本モジュール - Manual

JSON

PHP: JSON - Manual

About

json_encode/json_decodeをよく使う。非常に重要。

json_encodeはオブジェクトをJSON文字列表記にできるのでデバッグなどで便利。

PHPでUnicodeアンエスケープしたJSONを出力する関数 - オープンソースこねこね

JSON_UNESCAPED_UNICODE をオプションに指定しないと日本語はユニコードエスケープ表記になる。

連想配列
json_decode(
    string $json,
    ?bool $associative = null,
    int $depth = 512,
    int $flags = 0
): mixed

json_decodeは第二引数にtrueを指定しないと、object (stdClass) になる。trueにすると、連想配列になる。基本は連想配列でいいと思う。

Other

die/exit

dieはexitと完全に同等。

exit(string $status = ?): void
exit(int $status): void

メッセージを表示してスクリプトを終了する。関数ではなく、言語構造扱い。

statusを指定しない場合、丸括弧不要。status=0指定とみなされる。

statusが文字列なら、終了直前に表示する。intの場合、終了ステータス扱い。0-254。255は予約されている。0は正常終了。

eval

PHP: eval - Manual

文字列をPHPコードとして評価する。危険なので、特にユーザーから入力を受け付ける場合は、注意する。できれば使わないほうがいい。

evalで評価する文字列内でreturnした結果が返却値となる。ないならnull。戻り値が必要なら、テキスト内で忘れずにreturnする。

Topic

HTTP

PHPでHTTP通信をする方法がいくつかある。

  • file_get_contents
  • curl

file_get_contentsはPHP標準。curlは外部ライブラリー。

PHP cURL vs file_get_contents - Stack Overflow」などを見る限り、GET以外はcurlのほうが速くて複雑なことができるらしい。

file_get_contentsは元々ローカルや内部ファイルの読み込み用らしい。

GNU socialではHTTPClientクラス経由で実現するので、内部実装を意識する必要はない。

curlでのリクエスト方法を覚えておくと、汎用性が高い模様。

cURL

基本的な使用方法。

  1. curl_initでurlを指定してセッション初期化。
  2. curl_setopt/curl_setopt_arrayでオプションを設定。
    1. CURLOPT_POST=true/CURLOPT_POSTFIELDSでPOST関係指定。
    2. CURLOPT_RETURNTRANSFER=trueでcurl_execの応答ボディーをテキストで取得。
  3. curl_execで転送実行。CURLOPT_RETURNTRANSFER=trueを指定しない場合、true/falseのみ。
  4. curl_closeでセッション終了。
<?php

$ch = curl_init("http://www.example.com/");
$fp = fopen("example_homepage.txt", "w");

curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);
// curl_setopt_array($ch, $options);

curl_exec($ch);
if(curl_error($ch)) {
    fwrite($fp, curl_error($ch));
}

$info    = curl_getinfo($ch);
$errorNo = curl_errno($ch);

curl_close($ch);
fclose($fp);
?>
<?php

/* curlセッションを初期化する */
$ch = curl_init();

/* curlオプションを設定する */
curl_setopt($ch, CURLOPT_URL, "http://www.google.com/");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

/* curlを実行し、その内容を$result変数に保存 */
$result = curl_exec($ch);

/* curlセッションを終了する */
curl_close($ch);

/* result変数に保存した内容を表示 */
echo htmlspecialchars($result);


PHP: 定義済み定数 - Manual

特に重要なオプションがいくつかある。

  • CURLOPT_POST: trueならHTTP POST (application/x-www-form-urlencoded)。
  • CURLOPT_POSTFIELDS: POSTのリクエストボディー。文字列で渡すか、連想配列。連想配列の値が配列の場合、Content-Type: multipart/form-dataになる。ファイル送信はCURLFile (ファイル名) かCURLStringFile (ファイルの中身)を使う。
  • CURLINFO_HEADER_OUT => true: curl_getinfoにリクエストヘッダーを含める (web services - How to get info on sent PHP curl request - Stack Overflow)。

PHP: curl_getinfo - Manual

curl_getinfoで応答結果の詳細を確認できる。

特に以下は重要。

  • http_code
JSONデータの送受信

JSON形式のデータをPOST送受信する方法(PHP) | 合同会社スマート

  1. 連想配列でリクエストボディーのデータを作って、json_encodeでJSON文字列に変換。
  2. ヘッダーContent-Type指定: curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
  3. リクエストボディー指定: curl_setopt($ch, CURLOPT_POSTFIELDS, $data_json);
  4. 戻り値のデコード: $res_json = json_decode($result , true );

以上。

$data = array(
	'test1'=>'aaa',
	'test2'=> array(
		array(
			'test3'=>'bbb'
		)
	),
	'test4'=> array(
		array(
			'test5'=>'ccc',
			'test6'=>'ddd'
		)
	)
);

$data_json = json_encode($data);

$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_json);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, 'http://posttestserver.com/post.php');
$result=curl_exec($ch);
echo 'RETURN:'.$result;
curl_close($ch);

$result=curl_exec($ch);
$res_json = json_decode($result , true );
echo $res_json['return1'];