「PHP」の版間の差分

提供:senooken JP Wiki
(Variable Functions/可変関数/Callable/コールバック)
(同じ利用者による、間の47版が非表示)
113行目: 113行目:
「[https://packagist.org/search/?tags=search Packagist]」の検索結果をみても、tntsearchが特に人気の模様。
「[https://packagist.org/search/?tags=search Packagist]」の検索結果をみても、tntsearchが特に人気の模様。
== About==
== About==
=== About ===
PHPはプログラミング言語だ。汎用的なプログラミング言語だが、主にウェブサーバー上のソフトウェアで使用される。
PHPはプログラミング言語だ。汎用的なプログラミング言語だが、主にウェブサーバー上のソフトウェアで使用される。


120行目: 122行目:
*[https://www.php.net/manual/ja/langref.php PHP: 言語リファレンス - Manual]
*[https://www.php.net/manual/ja/langref.php PHP: 言語リファレンス - Manual]
ウェブ上にはPHPに関するTipsが多く公開されており、大抵の疑問はウェブ検索で解決できる。
ウェブ上にはPHPに関するTipsが多く公開されており、大抵の疑問はウェブ検索で解決できる。
==Version==
 
=== Version ===
PHPは言語の版数が上がる際、過去の版と互換性の無い破壊的変更がなされることがある。
PHPは言語の版数が上がる際、過去の版と互換性の無い破壊的変更がなされることがある。


126行目: 129行目:


GNU socialは現在PHP 7系で動作する様に記述されており、PHP 8系への対応は作業途中だ。
GNU socialは現在PHP 7系で動作する様に記述されており、PHP 8系への対応は作業途中だ。
===PHP v8===
 
==== PHP v8 ====
PHP v8になっていろいろ更新が入った。特にPHP v7.4からv8に更新する際のポイントがあるので整理する ([https://web.gnusocial.jp/post/2023/12/08/9535/ 行事: 「12月にPHP8.3が出るので、PHP8で増えた文法をおさらいしましょうセミナー」参加報告 | PHP8対応の肝は型とエラーレベル | GNU social JP Web])。
PHP v8になっていろいろ更新が入った。特にPHP v7.4からv8に更新する際のポイントがあるので整理する ([https://web.gnusocial.jp/post/2023/12/08/9535/ 行事: 「12月にPHP8.3が出るので、PHP8で増えた文法をおさらいしましょうセミナー」参加報告 | PHP8対応の肝は型とエラーレベル | GNU social JP Web])。


143行目: 147行目:
*Rector
*Rector
まず上記2個を試して、おまけでRectorも試すとよい。
まず上記2個を試して、おまけでRectorも試すとよい。
==Guide==
 
=== Guide ===
Ref:
Ref:
*[https://notabug.org/gnusocialjp/gnusocial/src/main/DOCUMENTATION/DEVELOPERS/CONTRIBUTING/coding_standards.md DOCUMENTATION/DEVELOPERS/CONTRIBUTING/coding_standards.md]
*[https://notabug.org/gnusocialjp/gnusocial/src/main/DOCUMENTATION/DEVELOPERS/CONTRIBUTING/coding_standards.md DOCUMENTATION/DEVELOPERS/CONTRIBUTING/coding_standards.md]
156行目: 161行目:


ただし、viewなど、表示に直接結びついているものは、小文字でもいいかも。ファイル名とURLパスが同じほうが分かりやすい。
ただし、viewなど、表示に直接結びついているものは、小文字でもいいかも。ファイル名とURLパスが同じほうが分かりやすい。
=== Tool ===
* [https://creatopia.jp/media/12102 PHPをブラウザで実行、動作確認できるおすすめツール4つ|Creatopia Media]
PHPをWebブラウザーで実行、動作確認のツールがいくつかある。
* 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]: パーマリンク。
3v4l.orgがパーマリンクがあって、複数バージョンの動作確認できるので、これがいいと思う。
==Package manager==
==Package manager==
===Composer===
===Composer===
206行目: 224行目:


composer.jsonを編集した場合、<code>composer dump-autoload</code>を実行して<code>vendor/autolaod.php</code>を必ず更新します。
composer.jsonを編集した場合、<code>composer dump-autoload</code>を実行して<code>vendor/autolaod.php</code>を必ず更新します。
==phpDocumentor==
Ref: [https://phpdoc.org/ Home | phpDocumentor].


PHPのソースコードにコメントを残す際に、構文に従って記載すると、ツールで表示したり、文書に出力できたりする。ソースコードリーディングにも役立つので、積極的に記載したほうがよさそう。構文を整理しておく。
==== Libraries ====
[https://getcomposer.org/doc/02-libraries.md Libraries - Composer]
 
自前のライブラリーをComposerでインストール可能な形式にする方法がある。


特に記法が大事。
===== Every project is a package =====
*[https://docs.phpdoc.org/3.0/guide/getting-started/what-is-a-docblock.html phpDocumentor]
ディレクトリーにcomposer.jsonがあると、そのディレクトリーはパッケージになる。プロジェクトとパッケージの違いは、名前の有無。プロジェクトは名前のないパッケージという扱いになる。
*[https://docs.phpdoc.org/3.0/guide/references/phpdoc/ phpDocumentor]
ファイル冒頭の<?php の直後あたりに書くと、ファイルレベルのDocBlockになる。逆にclassの直前などに書くと、ファイル冒頭でもclassレベルになる。


DocBlockは3部構成。
パッケージをインストール可能にするにあたって、'''composer.jsonに最低限名前'''が必要。
#Summary=短い説明。改行直前の.か空行で終わり。
{
#Description=長い説明。アルゴリズムの機能や、使用方法、例など。最初のタグか、改行、DocBlockの終端で終わる。
    "name": "acme/hello-world",
#Tags/Anntations=要素のメタ情報。新しい行の@から始まる。
    "require": {
具体例。<syntaxhighlight lang="php" line="1">
        "monolog/monolog": "1.0.*"
<?php
    }
/**
  }
* A summary informing the user what the associated element does.
acme/hello-worldというプロジェクトになる。acmeはベンダー名で、ベンダー名は必須。
  *
 
* A *description*, that can span multiple lines, to go _in-depth_ into
ベンダー名に迷う場合、GitHubのユーザー名が適している。'''パッケージ名は小文字必須'''。単語区切りは-にするのが慣例。
* the details of this element and to provide some background information
 
* or textual references.
===== Library Versioning =====
*
VCSでパッケージを管理している場合、composerはVCSからバージョンを自動で判別する。VCSを使っていない場合だけ、versionプロパティーを追加する。
* @param string $myArgument With a *description* of this argument,
*                          these may also span multiple lines.
*
* @return void
*/
function myFunction($myArgument)
  {
  {
    "version": "1.0.0"
  }
  }
</syntaxhighlight>以下の要素に前置できる。
*require(_once)
*include(_once)
*class
*interface
*trait
*function (including methods)
*property
*constant
*variables, both local and global scope.
Inheritance


DocBlockはSummary/Descriptionを上書きしたり、拡張できる。@inheritdocを使う。
===== 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 =====
{| class="wikitable"
.githubディレクトリーのように、パッケージに不要なファイルがある。
!Elements
!Inherited tags
|-
|''Any''
|[[/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]]
|-
|''Classes and Interfaces''
|[[/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]]
|-
|''Methods''
|[[/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]]
|-
|''Properties''
|[[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/var.html|@var]]
|}@subpackageタグは同じ@packageの親クラスのときだけ継承される。


よく使う@param/@returnの構文。
.gitattributesでパッケージやzipに含めないファイルを指定できる。
  <type-expression          = 1*(array-of-type-expression|array-of-type|type ["|"])
// .gitattributes
  array-of-type-expression = "(" type-expression ")[]"
  /demo export-ignore
  array-of-type            = type "[]"
phpunit.xml.dist export-ignore
  type                    = class-name|keyword
/.github/ export-ignore
  class-name              = 1*CHAR
以下のコマンドで確認できる。
  keyword                  = "string"|"integer"|"int"|"boolean"|"bool"|"float"
git archive branchName --format zip -o file.zip
                            |"double"|"object"|"mixed"|"array"|"resource"|"scalar"
パッケージに含まれないだけで、Gitリポジトリーには入っている。
                            |"void"|"null"|"callable"|"false"|"true"|"self"
 
==== 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はタグを扱う。


基本は @<directive> <Type> <name> <description> の書式。スペース区切り。
上記のようなタグの場合、composerは先頭のvなどのプレフィクスを除外して考える。基本的にはこの中で、一番新しいものを優先的に探す。
*@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のバージョンが決まっている。
====== branch ======
タグではなく、ブランチのチェックアウトが必要なら、特別なdev-*プレフィクス/サフィックスを指定してブランチを指定する。


PHP v7.4に対応してい最後のバージョンはPHPUnit 9なので、当分はこれを使うのが良い。
上記の例で、my-featureブランチの指定が必要ならば、dev-my-featureを指定する。
===Basic===
出典: [https://docs.phpunit.de/en/9.6/writing-tests-for-phpunit.html 2. Writing Tests for PHPUnit — PHPUnit 9.6 Manual]。


基本的な使用方法を整理する。
ブランチ名がバージョン名と似ている場合、記法が変わる。v1.x-devのように指定する。v1タグではなく、v1ブランチを明示するために、.xは必須。あるいは、タグ名とブランチ名を完全に別の名前 (v1ブランチの代わりにv1.xブランチ) にしておけば、.xは不要。
#基本的にはクラス単位で試験コードを記載。<Class>クラスの試験コードは<Class>Testの命名にする。
 
#<Class>Test はPHPUnit\Framwork\TestCaseを継承させる。
バージョン名によく似たブランチ名を指定する場合だけ、dev-プレイフィクスではなく、-devサフィックスを指定する。
#試験はpublicのtest*メソッドの命名にする。あるいは、@testのアノテーションを付ければ、命名規則に従わなくてもいい。
 
#test*メソッド内で、assertSame() などで、期待値との比較で試験を行う。
==phpDocumentor==
:
Ref: [https://phpdoc.org/ Home | phpDocumentor].
<?php declare(strict_types=1);
 
use PHPUnit\Framework\TestCase;
PHPのソースコードにコメントを残す際に、構文に従って記載すると、ツールで表示したり、文書に出力できたりする。ソースコードリーディングにも役立つので、積極的に記載したほうがよさそう。構文を整理しておく。
 
final class StackTest extends TestCase
特に記法が大事。
{
*[https://docs.phpdoc.org/3.0/guide/getting-started/what-is-a-docblock.html phpDocumentor]
<nowiki> </nowiki>  private static $dbh;
*[https://docs.phpdoc.org/3.0/guide/references/phpdoc/ phpDocumentor]
<nowiki> </nowiki>  private $instance;
ファイル冒頭の<?php の直後あたりに書くと、ファイルレベルのDocBlockになる。逆にclassの直前などに書くと、ファイル冒頭でもclassレベルになる。
<nowiki> </nowiki> 
 
<nowiki> </nowiki>  public static function setUpBeforeClass(): void
DocBlockは3部構成。
<nowiki> </nowiki>  {
#Summary=短い説明。改行直前の.か空行で終わり。
<nowiki> </nowiki>      // DB接続などクラス全体の初期化処理
#Description=長い説明。アルゴリズムの機能や、使用方法、例など。最初のタグか、改行、DocBlockの終端で終わる。
<nowiki> </nowiki>      self::$dbh = new PDO(<nowiki>''</nowiki>);
#Tags/Anntations=要素のメタ情報。新しい行の@から始まる。
<nowiki> </nowiki>  }
具体例。<syntaxhighlight lang="php" line="1">
<nowiki> </nowiki>  public static function tearDownAfterClass(): void
<?php
<nowiki> </nowiki>  {
/**
<nowiki> </nowiki>      self::$dbh = null;
  * A summary informing the user what the associated element does.
<nowiki> </nowiki>  }
  *
<nowiki> </nowiki>  protected function setUp(): void
  * A *description*, that can span multiple lines, to go _in-depth_ into
<nowiki> </nowiki>  {
  * the details of this element and to provide some background information
<nowiki> </nowiki>    // 該当インスタンスの生成などメソッド単位の初期化処理。
  * or textual references.
<nowiki> </nowiki>    $instance = new Stack();
  *
  <nowiki> </nowiki>  }
  * @param string $myArgument With a *description* of this argument,
  <nowiki> </nowiki>  public function testPushAndPop(): void
  *                          these may also span multiple lines.
  <nowiki> </nowiki>  {
  *
  <nowiki> </nowiki>      $stack = [];
  * @return void
  <nowiki> </nowiki>      $this->assertSame(0, count($stack));
  */
   
  function myFunction($myArgument)
  <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====
</syntaxhighlight>以下の要素に前置できる。
前回の試験で準備した結果を利用したい場合、@dependsのアノテーションでテスト関数を指定しておくと、指定したテスト関数のreturnを引数に受け継いだテスト関数を記述できる。
*require(_once)
 
*include(_once)
@dependsは複数指定でき、指定順に事前に試験が実行されて引数に渡される。
*class
====Data Provider====
*interface
ある関数に対して、テストケースを用意して、複数の引数の組み合わせを試験したいことがよくある。こういうときのために、テスト関数に渡す引数を生成する関数のデータプロバイダーを指定できる。@dataProviderで関数を指定する。データプロバイダーは引数のリストを配列で返すようにする。
*trait
*function (including methods)
*property
*constant
*variables, both local and global scope.
Inheritance


データ数が多い場合、名前付き配列にしておくと、どういうデータ項目で失敗したかがわかりやすい。
DocBlockはSummary/Descriptionを上書きしたり、拡張できる。@inheritdocを使う。


Iteratorオブジェクトを返してもいい。
要素ごとに以下のタグを継承する。
====Fixtures====
{| class="wikitable"
出典: [https://docs.phpunit.de/en/9.6/fixtures.html 4. Fixtures — PHPUnit 9.6 Manual]
!Elements
!Inherited tags
|-
|''Any''
|[[/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]]
|-
|''Classes and Interfaces''
|[[/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]]
|-
|''Methods''
|[[/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]]
|-
|''Properties''
|[[/wiki.gnusocial.jp//docs.phpdoc.org/3.0/guide/references/phpdoc/tags/var.html|@var]]
|}@subpackageタグは同じ@packageの親クラスのときだけ継承される。


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


テストメソッド実行前後に共通で行える処理がある。
クラス名以外は全小文字。
*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を対象にする最小限の例は以下。
基本は @<directive> <Type> <name> <description> の書式。スペース区切り。
<phpunit bootstrap="src/autoload.php">
*@property: クラスの注釈部で指定する。メンバー変数の説明。
  <testsuites>
==PHPUnit==
    <testsuite name="money">
PHPUnitを使用すれば自動単体テスト (Unit test) が可能だ。
      <directory>tests</directory>
*[https://web.gnusocial.jp/post/2023/07/07/7428/ 設置: PHPUnit | PHPの定番テストフレームワーク  |  GNU social JP Web]
    </testsuite>
===Version===
  </testsuites>
情報源: [https://phpunit.de/supported-versions.html Supported Versions of PHPUnit – The PHP Testing Framework]
</phpunit>
 
以下のように--testsuiteで試験対象を指定して実行する。
PHPUnitのバージョンごとに対応しているPHPのバージョンが決まっている。
phpunit --bootstrap src/autoload.php --testsuite money
 
===Assertions===
PHP v7.4に対応してい最後のバージョンはPHPUnit 9なので、当分はこれを使うのが良い。
Ref:
===Basic===
*[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 2. Writing Tests for PHPUnit — 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====
#基本的にはクラス単位で試験コードを記載。<Class>クラスの試験コードは<Class>Testの命名にする。
特に例外の試験がイレギュラー。
#<Class>Test はPHPUnit\Framwork\TestCaseを継承させる。
#試験はpublicのtest*メソッドの命名にする。あるいは、@testのアノテーションを付ければ、命名規則に従わなくてもいい。
#test*メソッド内で、assertSame() などで、期待値との比較で試験を行う。
例:
  <?php declare(strict_types=1);
  <?php declare(strict_types=1);
  use PHPUnit\Framework\TestCase;
  use PHPUnit\Framework\TestCase;
   
   
  final class ExceptionTest extends TestCase
  final class StackTest extends TestCase
  {
  {
    public function testException(): void
<nowiki> </nowiki>  private static $dbh;
     {
<nowiki> </nowiki>  private $instance;
        $this->expectException(InvalidArgumentException::class);
<nowiki> </nowiki> 
        // Run test target code following.
<nowiki> </nowiki>  public static function setUpBeforeClass(): void
    }
<nowiki> </nowiki>  {
  }
<nowiki> </nowiki>     // DB接続などクラス全体の初期化処理
上記のようにexpectExceptionを使う。
<nowiki> </nowiki>     self::$dbh = new PDO(<nowiki>''</nowiki>);
*expectException:
<nowiki> </nowiki>  }
*expectExceptionCode:
  <nowiki> </nowiki>  public static function tearDownAfterClass(): void
*expectExceptionMessage:
<nowiki> </nowiki>  {
*expectExceptionMessageMatches:
<nowiki> </nowiki>      self::$dbh = null;
例外が発生する処理の前に記述しておく。
<nowiki> </nowiki>  }
====Output====
<nowiki> </nowiki>  protected function setUp(): void
echoなど標準出力を試験する際も専用のメソッドがある。
<nowiki> </nowiki>  {
*<code>void expectOutputRegex(string $regularExpression)</code>
<nowiki> </nowiki>    // 該当インスタンスの生成などメソッド単位の初期化処理。
*<code>void expectOutputString(string $expectedString)</code>
<nowiki> </nowiki>    $instance = new Stack();
*<code>bool setOutputCallback(callable $callback)</code>
<nowiki> </nowiki>  }
*<code>string getActualOutput()</code>
<nowiki> </nowiki>   public function testPushAndPop(): void
expectExceptionと同様に事前にセットしておく。
<nowiki> </nowiki>  {
===Command-Line===
<nowiki> </nowiki>      $stack = [];
Ref: [https://docs.phpunit.de/en/9.6/textui.html 3. The Command-Line Test Runner — PHPUnit 9.6 Manual].
<nowiki> </nowiki>      $this->assertSame(0, count($stack));
 
phpunitコマンドでいろいろできる。いくつか重要なオプション、使用方法がある。
<nowiki> </nowiki>       array_push($stack, 'foo');
*phpunit file.php: 指定したファイルのテストを実行。
<nowiki> </nowiki>      $this->assertSame('foo', $stack[count($stack)-1]);
*--testsuite <name>: テストを指定。
<nowiki> </nowiki>      $this->assertSame(1, count($stack));
===Test Doubles===
Ref: [https://docs.phpunit.de/en/9.6/test-doubles.html 8. Test Doubles — PHPUnit 9.6 Manual].
<nowiki> </nowiki>       $this->assertSame('foo', array_pop($stack));
<nowiki> </nowiki>       $this->assertSame(0, count($stack));
<nowiki> </nowiki>   }
}
====Depends====
前回の試験で準備した結果を利用したい場合、@dependsのアノテーションでテスト関数を指定しておくと、指定したテスト関数のreturnを引数に受け継いだテスト関数を記述できる。
 
@dependsは複数指定でき、指定順に事前に試験が実行されて引数に渡される。
====Data Provider====
ある関数に対して、テストケースを用意して、複数の引数の組み合わせを試験したいことがよくある。こういうときのために、テスト関数に渡す引数を生成する関数のデータプロバイダーを指定できる。@dataProviderで関数を指定する。データプロバイダーは引数のリストを配列で返すようにする。


テスト時に、依存関係を模擬したもので置換したいことがある。PHPUnitにそういう仕組が用意されている。
データ数が多い場合、名前付き配列にしておくと、どういうデータ項目で失敗したかがわかりやすい。


stub=親、mock=子。
Iteratorオブジェクトを返してもいい。
====Fixtures====
出典: [https://docs.phpunit.de/en/9.6/fixtures.html 4. Fixtures — PHPUnit 9.6 Manual]。


メソッド内で他のクラス・メソッドを使う場合はmockで対象クラスを模擬させる。<syntaxhighlight lang="php">
テストメソッドの実行前に、テスト対象のインスタンスの生成や、DB接続など準備がいろいろある。これをFixturesと呼んでいる。この準備がけっこう手間になる。これを省力できるのがテストフレームワークの利点。
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;


final class SubjectTest extends TestCase
テストメソッド実行前後に共通で行える処理がある。
{
*setUp/tearDown: テストメソッド単位の前後処理。テスト対象インスタンスの生成など。tearDownは何もしなくてもいいことが多い。
    public function testObserversAreUpdated(): void
*setUpBeforeClass/tearDownAfterClass: クラス単位の前後処理。 DB接続など。
    {
====XML Configuration File====
        // Create a mock for the Observer class,
出典:
        // only mock the update() method.
*[https://docs.phpunit.de/en/9.6/organizing-tests.html 5. Organizing Tests — PHPUnit 9.6 Manual]
        $observer = $this->createMock(Observer::class);
*[https://docs.phpunit.de/en/9.6/configuration.html 3. The XML Configuration File — PHPUnit 9.6 Manual]
基本はコマンドでテスト対象クラス・ファイルを指定してテストを実行する。他に、XMLの設定ファイル (phpunit.xml) でもテスト対象を指定できる。


        // Set up the expectation for the update() method
testsディレクトリーの全*Test.phpを対象にする最小限の例は以下。
        // to be called only once and with the string 'something'
<phpunit bootstrap="src/autoload.php">
        // as its parameter.
  <testsuites>
        $observer->expects($this->once())
    <testsuite name="money">
                ->method('update')
      <directory>tests</directory>
                ->with($this->equalTo('something'));
    </testsuite>
 
  </testsuites>
        // Create a Subject object and attach the mocked
</phpunit>
        // Observer object to it.
以下のように--testsuiteで試験対象を指定して実行する。
        $subject = new Subject('My subject');
phpunit --bootstrap src/autoload.php --testsuite money
        $subject->attach($observer);
===Assertions===
 
Ref:
        // Call the doSomething() method on the $subject object
*[https://docs.phpunit.de/en/9.6/assertions.html 1. Assertions — PHPUnit 9.6 Manual].
        // which we expect to call the mocked Observer object's
*[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]
        // update() method with the string 'something'.
基本はassertSameでテストすればいいのだが、それ以外にも例外とかいろいろ試験したいケースがあるので、メソッドを整理する。
        $subject->doSomething();
====Exception====
    }
特に例外の試験がイレギュラー。
}
<?php declare(strict_types=1);
 
use PHPUnit\Framework\TestCase;
</syntaxhighlight>基本的な作り。
#createMock(<class>::class)で該当クラスのモックを作成。
final class ExceptionTest extends TestCase
#expectsに呼出回数条件のオブジェクトをセット。
{
#methodで対象メソッドを指定。
    public function testException(): void
#withで、該当メソッドの引数処理を指定。
    {
デフォルトで模擬実装はnullを返す。戻り値を変更したければ、will($this->returnValue())などを指定する。よく使うので短縮記法もある。
        $this->expectException(InvalidArgumentException::class);
{| class="wikitable"
        // Run test target code following.
|+Table 8.1 Stubbing short hands
    }
!short hand
}
!longer syntax
上記のようにexpectExceptionを使う。
|-
*expectException:
|<code>willReturn($value)</code>
*expectExceptionCode:
|<code>will($this->returnValue($value))</code>
*expectExceptionMessage:
|-
*expectExceptionMessageMatches:
|<code>willReturnArgument($argumentIndex)</code>
例外が発生する処理の前に記述しておく。
|<code>will($this->returnArgument($argumentIndex))</code>
====Output====
|-
echoなど標準出力を試験する際も専用のメソッドがある。
|<code>willReturnCallback($callback)</code>
*<code>void expectOutputRegex(string $regularExpression)</code>
|<code>will($this->returnCallback($callback))</code>
*<code>void expectOutputString(string $expectedString)</code>
|-
*<code>bool setOutputCallback(callable $callback)</code>
|<code>willReturnMap($valueMap)</code>
*<code>string getActualOutput()</code>
|<code>will($this->returnValueMap($valueMap))</code>
expectExceptionと同様に事前にセットしておく。
|-
===Command-Line===
|<code>willReturnOnConsecutiveCalls($value1, $value2)</code>
Ref: [https://docs.phpunit.de/en/9.6/textui.html 3. The Command-Line Test Runner — PHPUnit 9.6 Manual].
|<code>will($this->onConsecutiveCalls($value1, $value2))</code>
 
|-
phpunitコマンドでいろいろできる。いくつか重要なオプション、使用方法がある。
|<code>willReturnSelf()</code>
*phpunit file.php: 指定したファイルのテストを実行。
|<code>will($this->returnSelf())</code>
*--testsuite <name>: テストを指定。
|-
===Test Doubles===
|<code>willThrowException($exception)</code>
Ref: [https://docs.phpunit.de/en/9.6/test-doubles.html 8. Test Doubles — PHPUnit 9.6 Manual].
|<code>will($this->throwException($exception))</code>
 
|}willReturnCallbackで呼び出し関数をまるごと別のものに置換できる。これが非常に便利。
テスト時に、依存関係を模擬したもので置換したいことがある。PHPUnitにそういう仕組が用意されている。
===Topic===
 
====Test private/protected====
stub=親、mock=子。
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]
メソッド内で他のクラス・メソッドを使う場合はmockで対象クラスを模擬させる。<syntaxhighlight lang="php">
*[https://zenn.dev/ttskch/articles/c7dcd5c1188cdd PHPUnitでprivateメソッドをテストする]
<?php declare(strict_types=1);
*[https://qiita.com/ponsuke0531/items/6dc6fc34fff1e9b37901 privateとprotectedメソッドをPHPUnitでテストする方法 #PHP - Qiita]
use PHPUnit\Framework\TestCase;
クラスのprivate/protectedメソッドのテストには工夫が必要となる。<syntaxhighlight lang="php">
 
    /**
final class SubjectTest extends TestCase
    * privateメソッドを実行する.
{
    * @param string $methodName privateメソッドの名前
     public function testObserversAreUpdated(): void
    * @param array $param privateメソッドに渡す引数
     {
    * @return mixed 実行結果
         // Create a mock for the Observer class,
    * @throws \ReflectionException 引数のクラスがない場合に発生.
         // only mock the update() method.
    */
         $observer = $this->createMock(Observer::class);
     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の標準出力と干渉して以下のエラーが出て試験できない。
        // Set up the expectation for the update() method
Cannot modify header information - headers already sent by (output started at .../vendor/phpunit/phpunit/src/Util/Printer.php:138)
        // to be called only once and with the string 'something'
回避方法が2種類ある。
        // as its parameter.
#<code>@runInSeparateProcess</code>
        $observer->expects($this->once())
#phpunit --stderr
                ->method('update')
1個目のアノテーションをテストメソッドに指定すると別プロセスでの実行になる。ただ、プロセス生成は時間がかかるため、試験が多いと効率が悪い。
                ->with($this->equalTo('something'));


2個目のphpunitの出力を標準エラーに出力させる方法がシンプルで効率もいい。phpunit.xmlに stderr="true"を指定するとキー入力を省略できる。こちらで対応しよう。
        // Create a Subject object and attach the mocked
====Test exit====
        // Observer object to it.
Ref:
        $subject = new Subject('My subject');
*[https://qiita.com/kumagaias/items/5b1d95a897bae11f2a5a PHP でテストコードを意識したコーディング #PHPUnit - Qiita]
        $subject->attach($observer);
*[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内でこれらがあると、テストも強制終了になる。


上記の別プロセスで実行していた場合、以下のエラーになる。
        // Call the doSomething() method on the $subject object
Test was run in child process and ended unexpectedly
        // which we expect to call the mocked Observer object's
対処方法がいくつかある。
        // update() method with the string 'something'.
#exitを使わないコードに変更。
        $subject->doSomething();
#isTestのようなフラグを元コードに入れてテスト可否で分岐してexitを回避。
    }
#execで外部プロセスで実行してexitCodeを試験。
}
#exit/die部分だけ別関数に抽出してmockで置換?
<https://notabug.org/gnusocialjp/gnusocial/src/main/actions/apiaccountregister.php> のclientErrorが内部でexitする。


このclientErrorをwillなどで置換すればよさそう?
</syntaxhighlight>基本的な作り。
==Language Reference==
#createMock(<class>::class)で該当クラスのモックを作成。
===Types===
#expectsに呼出回数条件のオブジェクトをセット。
====Introduction====
#methodで対象メソッドを指定。
Ref: [https://www.php.net/manual/en/language.types.intro.php PHP: Introduction - Manual].
#withで、該当メソッドの引数処理を指定。
 
デフォルトで模擬実装はnullを返す。戻り値を変更したければ、will($this->returnValue())などを指定する。よく使うので短縮記法もある。
PHPの変数は以下の型のいずれかの値となる。
{| class="wikitable"
*null
|+Table 8.1 Stubbing short hands
*bool
!short hand
*int
!longer syntax
*float (floating-point number)
|-
*string
|<code>willReturn($value)</code>
*array
|<code>will($this->returnValue($value))</code>
*object
|-
*callable
|<code>willReturnArgument($argumentIndex)</code>
*resourceC言語のようなlong/doubleのような精度ごとの型はない。
|<code>will($this->returnArgument($argumentIndex))</code>
====Boolean====
|-
*[https://www.php.net/manual/ja/language.types.boolean.php PHP: 論理型 (boolean) - Manual]
|<code>willReturnCallback($callback)</code>
*[https://qiita.com/minato-naka/items/50645a45998e91c83e2b PHP trueになるもの・falseになるもの確認 falseを入れた配列はどっちに? #初心者 - Qiita]
|<code>will($this->returnCallback($callback))</code>
条件判定にかかってくるので非常に重要。
|-
 
|<code>willReturnMap($valueMap)</code>
まずは、下記のfalseになるもの一覧を把握し、それ以外はすべてtrueになるということを把握しておく。
|<code>will($this->returnValueMap($valueMap))</code>
*booleanのfalse
|-
*intの0
|<code>willReturnOnConsecutiveCalls($value1, $value2)</code>
*floatの0.0
|<code>will($this->onConsecutiveCalls($value1, $value2))</code>
*stringの空文字列、"0"
|-
*要素数0個のarray
|<code>willReturnSelf()</code>
*null (未初期化変数含む)
|<code>will($this->returnSelf())</code>
stringの"0"と要素0のarrayがfalseになる点が重要。注意する。要素0のarrayは包含判定、検索などでよく使う。
|-
 
|<code>willThrowException($exception)</code>
stringの0ははまりどころ。stringは何がくるかわからないなら、strlenで文字数を見たほうが確実。
|<code>will($this->throwException($exception))</code>
====Strings====
|}willReturnCallbackで呼び出し関数をまるごと別のものに置換できる。これが非常に便利。
Ref: [https://www.php.net/manual/ja/language.types.string.php PHP: 文字列 - Manual].
===Topic===
 
====Test private/protected====
非常に重要。
Ref:
=====Literal=====
*[https://stackoverflow.com/questions/249664/best-practices-to-test-protected-methods-with-phpunit php - Best practices to test protected methods with PHPUnit - Stack Overflow]
文字列リテラルとしては4の表現がある。
*[https://zenn.dev/ttskch/articles/c7dcd5c1188cdd PHPUnitでprivateメソッドをテストする]
*Single quote: <nowiki>''</nowiki> 変数展開されない。
*[https://qiita.com/ponsuke0531/items/6dc6fc34fff1e9b37901 privateとprotectedメソッドをPHPUnitでテストする方法 #PHP - Qiita]
*Double quote: "" 変数展開される。
クラスのprivate/protectedメソッドのテストには工夫が必要となる。<syntaxhighlight lang="php">
*Here document:  <<<EOT 変数展開される。
    /**
*Nowdoc: <<'EOT' 変数展開されない。
    * privateメソッドを実行する.
引用符内で引用符'を使う場合はバックスラッシュ\でエスケープが必要。バックスラッシュ自体の指定は二重\\。
    * @param string $methodName privateメソッドの名前
 
    * @param array $param privateメソッドに渡す引数
Here document/Nowdocは終端IDのインデントで行頭を識別しており、インデントに意味があるので注意する。
    * @return mixed 実行結果
=====Parse=====
    * @throws \ReflectionException 引数のクラスがない場合に発生.
二重引用符とヒアドキュメントではエスケープシーケンスが解釈され、変数が展開される。<syntaxhighlight lang="php">
    */
<?php
    private function doMethod(string $methodName, array $param)
$juice = "apple";
    {
 
        // テスト対象のクラスをnewする.
echo "He drank some $juice juice." . PHP_EOL;
        $controller = $this->instance;
 
        // ReflectionClassをテスト対象のクラスをもとに作る.
// 意図しない動作をします。"s" は、変数名として有効な文字です。よって、変数は $juices を参照しています。$juice ではありません。
        $reflection = new \ReflectionClass($controller);
echo "He drank some juice made of $juices." . PHP_EOL;
        // メソッドを取得する.
        $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の標準出力と干渉して以下のエラーが出て試験できない。
echo "He drank some juice made of {$juice}s.";
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"を指定するとキー入力を省略できる。こちらで対応しよう。
$var1=9;
====Test exit====
echo "{${mb_strtolower('VAR1')}}"; // 9
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などで置換すればよさそう?
</syntaxhighlight>波括弧はなくてもいいが、文字列が連結するなどして変数名の終端を区別できない場合に必須になる。
==Language Reference==
===Types===
====Introduction====
Ref: [https://www.php.net/manual/en/language.types.intro.php PHP: Introduction - Manual].


<nowiki>さらに複雑なことができる。{${}}を指定すると、内側の波括弧内で、${}部分が変数評価になる場合にだけ式を指定できる。動きがトリッキーすぎる。フォーマット文字列的なことには使えない。バグのもとになりそうなので使用を控えたほうがよさそう。</nowiki>
PHPの変数は以下の型のいずれかの値となる。
=====Format=====
*null
Ref:
*bool
*[https://chaika.hatenablog.com/entry/2023/02/14/120000 PHP 文字列中に変数で値を埋め込むやつのメモ - かもメモ]
*int
*[https://qiita.com/iez/items/8a36e5cba8bf6af21f45 python の string.format が便利だからPHPでも使いたい #PHP - Qiita]
*float (floating-point number)
PHPにはPythonのformatメソッド相当はない。が似たような目的の関数がある。
*string
*sprintf/vprintf
*array
*strtr
*object
strtrは第2引数にold => newの置換のペアの配列を渡す。やることは同じようなものだけどちょっと違う。
*callable
*resource
C言語のようなlong/doubleのような精度ごとの型はない。
 
==== System ====
[https://www.php.net/manual/ja/language.types.type-system.php PHP: 型システム - Manual]


vsprintfは置換対象が可変長引数ではなく配列なだけ。
組込型の他に、ユーザー定義の型、aliasなどいくつかの型がある。
=====sprintf=====
Ref: [https://www.php.net/manual/ja/function.sprintf.php PHP: sprintf - Manual]


今後何度も使う。
===== 基本型 =====
言語に統合されていて、ユーザー定義で再現不能。


C言語のprintfといろいろ違うところがある。<syntaxhighlight lang="php">
* 組込
<?php
** null
$format = 'The %2$s contains %1$d monkeys.
** スカラー型: bool/int/float/string
          That\'s a nice %2$s full of %1$d monkeys.';
** array
echo sprintf($format, $num, $location);
** object
** resource
** never
** void
** クラス内の相対型: self/parent/static
* Value型: false/true
* ユーザー定義型/クラス型
** インターフェイス
** クラス
** 列挙型
* callable


echo sprintf("%'.9d\n", 123); // ......123
===== 複合型 =====
echo sprintf("%'.09d\n", 123); // 000000123
複数の基本型を組み合わせた型。交差型とunion型で作れる。


?>
* 交差型: 宣言した複数のクラス型をすべて満たす型。&で表現。T/U/Vの交差型はT&U&Vと書く。
</syntaxhighlight>特徴的なのが`%数$指定子`で引数の番号を選べるところ。Pythonの`{数:指定子}`に似ている。
* union型: 複数の型を受け入れる型。|で表現。T/U/Vのunion型はT|U|Vと書く。交差型を含む場合、T|(X&U)と丸括弧で囲む必要がある。


後は埋める文字を指定する際は'を前置。
===== alias =====
=====文字列の切り出し=====
PHPはmixedとiterableの2個の型のエイリアスに対応している。
いくつか方法がある。
 
*substr/mb_substr
* mixed=object|resource|array|string|float|int|bool|null: PHP 8.0.0で導入。mixedは型のトップ。他の全部の型はこの型の部分になる。
*strpos/mb_strpos/strrpos/mb_strrpos ([https://www.php.net/manual/ja/function.strpos.php PHP: strpos - Manual])
* iterable=Traversable|array: PHP 7.1.0で導入。foreachで反復可能でジェネレーター内でyield from可能。
*split
*preg_match ([https://www.php.net/manual/ja/function.preg-match.php PHP: preg_match - Manual])
preg_matchの自由度が高い。速度を気にしなくていいならこれでいいと思われる。ただ、引数の配列に入ってくるのがいまいち。関数の戻り値でほしい。


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未満なら以下のようなコード。
====Boolean====
function startsWith( $haystack, $needle ) {
*[https://www.php.net/manual/ja/language.types.boolean.php PHP: 論理型 (boolean) - Manual]
      $length = strlen( $needle );
*[https://qiita.com/minato-naka/items/50645a45998e91c83e2b PHP trueになるもの・falseになるもの確認 falseを入れた配列はどっちに? #初心者 - Qiita]
      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でマルチバイト対応。


===== 改行分割 =====
まずは、下記のfalseになるもの一覧を把握し、それ以外はすべてtrueになるということを把握しておく。
*booleanのfalse
*intの0
*floatの0.0
*stringの空文字列、"0"
*要素数0個のarray
*null (未初期化変数含む)
stringの"0"と要素0のarrayがfalseになる点が重要。注意する。要素0のarrayは包含判定、検索などでよく使う。


* [https://stackoverflow.com/questions/3997336/explode-php-string-by-new-line Explode PHP string by new line - Stack Overflow]
stringの0ははまりどころ。stringは何がくるかわからないなら、strlenで文字数を見たほうが確実。
* [https://stackoverflow.com/questions/1483497/split-string-by-new-line-characters php - Split string by new line characters - Stack Overflow]
====Strings====
* [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]
Ref: [https://www.php.net/manual/ja/language.types.string.php PHP: 文字列 - Manual].


<code>explode('\n', $csv)</code> のようなことをしたくなるが、改行が\nとは限らない。
非常に重要。
  $array = preg_split('/\R/u', $string);
=====Literal=====
上記がいい。\Rが\r \n \n\rなどにマッチ。uで入力がUTF-8の場合を考慮。例えば、「腰」がuをつけないと分割されてしまう。
文字列リテラルとしては4の表現がある。
*Single quote: <nowiki>''</nowiki> 変数展開されない。
*Double quote: "" 変数展開される。
*Here document: <<<EOT 二重引用符扱いで変数展開される。
*Nowdoc: <<<'EOT' 一重引用符扱いで変数展開されない。
引用符内で引用符'を使う場合はバックスラッシュ\でエスケープが必要。バックスラッシュ自体の指定は二重\\


===== trim =====
Here document/Nowdocは終端IDのインデントで行頭を識別しており、インデントに意味があるので注意する。
[https://www.php.net/manual/ja/function.trim.php PHP: trim - Manual]
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;


====Array====
=====Parse=====
=====Create=====
二重引用符とヒアドキュメントではエスケープシーケンスが解釈され、変数が展開される。<syntaxhighlight lang="php">
配列の作成方法がいくつかある。
<?php
*array()/[]
$juice = "apple";
*explode ([https://www.php.net/manual/ja/function.explode.php PHP: explode - Manual])


===== Read =====
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]}";


====== 末尾要素 ======
// 動作します。多次元配列を使用する際は、
[https://hishikiryu.com/php-get-last-array-value/ 【PHP】配列の最後(末尾)の要素を取得まとめ array_key_last, count, end関数 | ヒシキリュウ.com]
// 文字列の中では必ず配列を波括弧で囲むようにします。
echo "This works: {$arr['foo'][3]}";


* array_key_last: PHP v7.3.0+ ($arr[array_key_last($arr)];)。
// 複雑な例。二重展開で変数になる場合だけ式が使える模様。
* count: 昔ながら ($arr[count($arr) - 1];)。
$var1=9;
* end: 非推奨。
echo "{${mb_strtolower('VAR1')}}"; // 9


=====Append=====
?>
配列要素の追加。
</syntaxhighlight>波括弧はなくてもいいが、文字列が連結するなどして変数名の終端を区別できない場合に必須になる。
$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 ([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);   
    }
}
print_r($ar);


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]


配列変数を代入すると通常はそれでコピーになる。ただし、配列にオブジェクトがあると、そのオブジェクトはシャローコピーになる。
* ""内だと、連想配列添字の引用符不能。
$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では配列の終端カンマは許容される。


他にも、名前空間のグループ指定は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に変換する。


[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]
<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の置換のペアの配列を渡す。やることは同じようなものだけどちょっと違う。


[https://qiita.com/ktz_alias/items/16241bdf11757b556cdb 連想配列をキーと値のペアの配列にするちょっと気のきいた方法(かも) #PHP - Qiita]
vsprintfは置換対象が可変長引数ではなく配列なだけ。
*$array = array_values($array);: 値だけを1次元にしたい場合。
=====sprintf=====
*array_map(null, array_keys($a1), array_values($a1));: 連想配列の[[key,value], [key2, valu2]] 形式。
Ref: [https://www.php.net/manual/ja/function.sprintf.php PHP: sprintf - Manual]
後者のパターンはそれなりに使う気がする。
======単純配列→連想配列======
[https://www.techiedelight.com/ja/convert-regular-array-to-associative-array-php/ PHP で通常の配列を連想配列に変換する]


いくつか方法がある。
今後何度も使う。
#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初期化などしたい場合。


====== CSV→連想配列 ======
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);


* [https://medium.com/@czmole/php-convert-csv-to-associative-arrays-b82b9b4d4412 PHP convert CSV to associative arrays | by Catalin ZMOLE 👨‍💻 | Medium]
echo sprintf("%'.9d\n", 123); // ......123
* [https://steindom.com/2012/12/08/shortest-php-code-convert-csv-associative-array Shortest PHP code to convert CSV to associative array | Steindom]
echo sprintf("%'.09d\n", 123); // 000000123
* [https://www.php.net/manual/en/function.str-getcsv.php PHP: str_getcsv - Manual]


CSVを、よくDBの取得結果の形式の、行単位連想配列に変換する。方法がいくつかある。
?>
<?php
</syntaxhighlight>特徴的なのが`%数$指定子`で引数の番号を選べるところ。Pythonの`{数:指定子}`に似ている。
$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) {
*substr/mb_substr
  $csv[] = array_combine($header, $row);
*strpos/mb_strpos/strrpos/mb_strrpos ([https://www.php.net/manual/ja/function.strpos.php PHP: strpos - Manual])
}
*split
1番目の方法がシンプル。
*preg_match ([https://www.php.net/manual/ja/function.preg-match.php PHP: preg_match - Manual])
preg_matchの自由度が高い。速度を気にしなくていいならこれでいいと思われる。ただ、引数の配列に入ってくるのがいまいち。関数の戻り値でほしい。


=====Search=====
strposとsubstrを組み合わせると端の文字列を切り出せる。
[https://www.sejuku.net/blog/22098 【PHP入門】配列の値を検索するarray_searchと他4つの関数 | 侍エンジニアブログ]
=====文字列置換=====
 
*str_replace
いくつか方法がある。
*substr_replace: 日本語不能。
 
*preg_replace
基本はin_array。
*explode/implode
======array_key_exists======
substr_replace($text, <nowiki>''</nowiki>, -1);
[https://www.php.net/manual/ja/function.array-key-exists.php PHP: array_key_exists - Manual]
substr_replace($text, '.', nb_strrpos('_'));
 
1文字などの置換ならmb_strrposとの組み合わせ。
配列のキーの存在確認のほうほうがいくつかある。
$query = $request->query();
*array_key_exits: array_key_exists('first', $search_array);
foreach ($query as $key => $value) {
*isset: nullだとfalseになる (isset($search_array['first']))。
unset($query[$key]);
*empty: nullだとfalseになる。
$keys = explode('_', $key);
基本はarray_key_exits。
$key = implode('_', array_slice($keys, 0, -1)) . '.' . $keys[count($keys)-1];
======空確認======
$query[$key] = $value;
[https://qiita.com/miriwo/items/c4760cbb2807ee84ef2d PHP 配列が空かどうかを判定する #初心者 - Qiita]
}
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]」がある。


emptyで確認できる。が、単にnullなどの場合も判定してしまう。null or emptyという意味ならemptyでもOK。
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でマルチバイト対応。


逆に、issetであることと、nullではないことを確認できる。
===== 改行分割 =====
======array_search======
array_search() - 指定した値を配列で検索し、見つかった場合に対応する最初のキーを返す


全てのキーが必要なら、array_keysにfilter_valueを指定する。
* [https://stackoverflow.com/questions/3997336/explode-php-string-by-new-line Explode PHP string by new line - Stack Overflow]
======in_array======
* [https://stackoverflow.com/questions/1483497/split-string-by-new-line-characters php - Split string by new line characters - Stack Overflow]
in_array — 配列に値があるかチェックする
* [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]
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]]
<code>haystack</code> 内の <code>needle</code> を検索します。 <code>strict</code> が設定されていない限りは型の比較は行いません。


基本は$strict=trueで指定したほうがいい。完全一致検索。
<code>explode('\n', $csv)</code> のようなことをしたくなるが、改行が\nとは限らない。
======any/all/some/every======
$array = preg_split('/\R/u', $string);
[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]
上記がいい。\Rが\r \n \n\rなどにマッチ。uで入力がUTF-8の場合を考慮。例えば、「腰」がuをつけないと分割されてしまう。


配列に対する、1個または全部の評価。
===== trim =====
[https://www.php.net/manual/ja/function.trim.php PHP: trim - Manual]


JavaScriptのsome/every相当。
文字列の両端のホワイトスペースを除去する。


PHP 8.4ならarray_any/array_allが存在する。
====Array====
 
=====Create=====
PHP 8.4未満なら、いくつか方法がある。
配列の作成方法がいくつかある。
function array_any(array $array, callable $fn) {
*array()/[]
    foreach ($array as $value) {
*explode ([https://www.php.net/manual/ja/function.explode.php PHP: explode - Manual])
        if($fn($value)) {
 
            return true;
===== Read =====
        }
    }
    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);
[https://hishikiryu.com/php-get-last-array-value/ 【PHP】配列の最後(末尾)の要素を取得まとめ array_key_last, count, end関数 | ヒシキリュウ.com]
    return count($result) > 0;
}
$myarray = [2, 5, 8, 12, 4];
array_some($myarray, function($value) {
    return $value > 10;
}); // true
foreachで途中で終わるほうが速い模様。
======配列同士の包含・交差判定======
*[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]
1個でも入っているかを見たければ、array_intersect ([https://www.php.net/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_key_last: PHP v7.3.0+ ($arr[array_key_last($arr)];)。
* count: 昔ながら ($arr[count($arr) - 1];)
* end: 非推奨。


全部の包含判定したい場合、array_diff ([https://www.php.net/manual/ja/function.array-diff.php PHP: array_diff - Manual]) でできる。
=====Append=====
  $containsAllValues = !array_diff($search_this, $all);
配列要素の追加。
array_diffはarray_intersectと異なり、1個目の配列要素の内、2個目の不在要素を返す (差分)。なので、空なら全包含となる。非空なら非全包含=some。
$arr[キー] = 値;
 
$arr[] = 値;
完全一致なら、===でOK。
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 ([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);   
    }
}
print_r($ar);


ポイントとしては、1個目の要素は要素数が少ない配列を指定したほうが速くなる。判定だけで、速度が重要なら、foreachで見つかったらすぐreturnしたほうが速い。
012Array
 
(
array_intersectが実行結果とboolが同じ向きなので、これを使うとわかりやすいだろう。
    [0] => 1
=====Array Functions=====
    [1] => 2
======compact======
)
Ref: [https://www.php.net/manual/ja/function.compact.php PHP: compact - Manual].
途中で削除しても、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]


変数名とその値から、配列を作る。extractの逆。
配列変数を代入すると通常はそれでコピーになる。ただし、配列にオブジェクトがあると、そのオブジェクトはシャローコピーになる。
$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では配列の終端カンマは許容される。


MVCのViewに複数の値を渡す場合などによく使う。
他にも、名前空間のグループ指定はPHP7.2以上、関数の引数はPHP7.3以上で可能になった。
======extract======
=====連想配列判定=====
Ref: [https://www.php.net/manual/en/function.extract.php PHP: extract - Manual].
[https://qiita.com/Hiraku/items/721cc3a385cb2d7daebd 配列か連想配列か判定する #PHP - Qiita]
<?php
if (array_values($arr) === $arr) {
  echo '$arrは配列';
} else {
  echo '$arrは連想配列';
}
これで添え字が、数字かどうかをみるのがいい模様。
=====Convert=====
======連想配列→単純配列======
associative arrayをsimple arrayに変換する。


配列のキー・バリューを変数として取り込む。
[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]
======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相当。非常に重要でよく使う。


callbackにnullを指定すると、複数の配列のzip (unpack) を行う。
[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 で通常の配列を連想配列に変換する]


ただ、array_mapのコールバックの引数は通常配列の要素が想定されていて、連想配列のキーにはアクセスできない。
いくつか方法がある。
 
#array_combine
それをしたかったら、array_reduceを使う。らしい。
#array_fill_keys
#foreach
#array_flip
$ar = ['a', 'b'];
$ar2 = array_combine($ar, $ar);
var_dump($ar2);
array_combineがシンプル。array_fill_keysは0初期化などしたい場合。


[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]
====== CSV→連想配列 ======
======array_flip======
[https://www.php.net/manual/ja/function.array-flip.php PHP: array_flip - Manual]


配列のキーと値を反転した配列を返す。元のarrayの値は有効なキーを必要とする。つまり、intかstring。型が違う場合、警告が出て無視される。
* [https://medium.com/@czmole/php-convert-csv-to-associative-arrays-b82b9b4d4412 PHP convert CSV to associative arrays | by Catalin ZMOLE 👨‍💻 | Medium]
* [https://steindom.com/2012/12/08/shortest-php-code-convert-csv-associative-array Shortest PHP code to convert CSV to associative array | Steindom]
* [https://www.php.net/manual/en/function.str-getcsv.php PHP: str_getcsv - Manual]


また、同じ値が複数ある場合、最後のみが有効になる。
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
?>


====Type declarations/型宣言====
$rows = array_map('str_getcsv', file('myfile.csv'));
[https://www.php.net/manual/ja/language.types.declarations.php PHP: 型宣言 - Manual]
$header = array_shift($rows);
 
  $csv = array();
関数の引数、戻り値、クラスのプロパティー (PHP 7.4.0以上) に型を宣言できる。これにより、型を保証でき、その型でなければ、TypeErrorをスローする。
  foreach ($rows as $row) {
 
  $csv[] = array_combine($header, $row);
関数の戻り値だけ、型の指定箇所がやや特殊で、それ以外は原則変数の直前。関数の戻り値の場合、(): の後に指定する。
  function name(): type {}
 
  <?php
function sum($a, $b): float {
    return $a + $b;
  }
  }
1番目の方法がシンプル。
// float が返される点に注意
var_dump(sum(1, 2));
?>
nullable な型とシンタックスシュガー


nullableの場合、型名の前に?を指定する (PHP 7.1.0以上)。?TとT|nullは同じ意味。
=====Search=====
[https://www.sejuku.net/blog/22098 【PHP入門】配列の値を検索するarray_searchと他4つの関数 | 侍エンジニアブログ]


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


注意: この文法は、PHP 7.1.0 以降でサポートされており、 PHP 8.0で一般化された union 型がサポートされる前から存在します。
基本はin_array。
======array_key_exists/キー確認======
[https://www.php.net/manual/ja/function.array-key-exists.php PHP: array_key_exists - Manual]


PHP 7.4未満などの場合は、しかたないのでアノテーションで対応する。
配列のキーの存在確認のほうほうがいくつかある。
====Type juggling====
*array_key_exits: array_key_exists('first', $search_array);
[https://www.php.net/manual/ja/language.types.type-juggling.php PHP: 型の相互変換 - Manual]
*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]


型の相互変換。非常に重要。
emptyで確認できる。が、単にnullなどの場合も判定してしまう。null or emptyという意味ならemptyでもOK。


いろいろ方法がある。
逆に、issetであることと、nullではないことを確認できる。
======array_search======
array_search() - 指定した値を配列で検索し、見つかった場合に対応する最初のキーを返す


共通なのはキャスト (cast)。<syntaxhighlight lang="php">
全てのキーが必要なら、array_keysにfilter_valueを指定する。
<?php
======in_array======
$foo = 10;  // $foo は整数です
in_array — 配列に値があるかチェックする
$bar = (bool) $foo;  // $bar は boolean です
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]]
$fst = "$foo"; // to string.
<code>haystack</code> 内の <code>needle</code> を検索します。 <code>strict</code> が設定されていない限りは型の比較は行いません。
?>
</syntaxhighlight>C言語と同じで (型) を前置する。ただし、少々長い。


文字列への変換は二重引用符囲などもOK。まあ、キャストだけ覚えておくのがシンプル。
基本は$strict=trueで指定したほうがいい。完全一致検索。
====Other====
======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]
[https://www.php.net/manual/ja/function.gettype.php PHP: gettype - Manual]
 
配列に対する、1個または全部の評価。


PHPでの型確認・判定方法がいくつかある。
JavaScriptのsome/every相当。
*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: [https://www.php.net/manual/en/language.variables.basics.php PHP: Basics - Manual].
=====Undefined variable=====
未定義変数 (undefined variable) の値はNULL。


未定義変数にアクセスすると、E_WARNING (PHP 8未満はE_NOTICE) レベルのエラーが生じる。回避したければ、isset()で検知する。
PHP 8.4ならarray_any/array_allが存在する。


未定義変数の検知・制御方法がいくつかある。
PHP 8.4未満なら、いくつか方法がある。
*isset ([https://www.php.net/manual/en/function.isset.php PHP: isset - Manual])
function array_any(array $array, callable $fn) {
*empty ([https://www.php.net/manual/en/function.empty.php PHP: empty - Manual])
    foreach ($array as $value) {
* ??: Null 合体演算子/Null collapsing operator
        if($fn($value)) {
* ??=: NULL合体代入演算子 PHP v7.4以上。
            return true;
*@: エラー制御演算子
        }
issetとempty、Null合体演算子あたりをメインで使う。特にempty。
    }
    return false;
}
function array_every(array $array, callable $fn) {
    foreach ($array as $value) {
        if(!$fn($value)) {
            return false;
        }
    }
    return true;
}


emptyは以下相当を実施してくれる。
  function array_some(array $data, callable $callback) {
  !(isset($var) && $var)
    $result = array_filter($data, $callback);
  !isset($var) || $var == false
    return count($result) > 0;
isset($var) && $varは頻繁に使うことになるだろうから、emptyで短縮できる。
}
  empty($var) ? false : true;
  $var ?? false;
  $myarray = [2, 5, 8, 12, 4];
emptyとissetは関係が逆に似ているがissetは挙動が違う。
array_some($myarray, function($value) {
    return $value > 10;
}); // true
foreachで途中で終わるほうが速い模様。
======配列同士の包含・交差判定======
*[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]
1個でも入っているかを見たければ、array_intersect ([https://www.php.net/array_intersect PHP: array_intersect - Manual]) がこの目的に合致する。
  $peopleContainsCriminal = !empty(array_intersect($people, $criminals));
  $peopleContainsCriminal = array_intersect($people, $criminals);
$criminalsの配列に、$peopleの要素のいずれかが入っているかを上記で判断できる。


「Returns true if var exists and has any value other than null. false otherwise.」なので、変数の値を評価はしない。nullかどうかだけしかみない。emptyとは扱いが違うので注意する。
array_intersectは1個目の配列要素の内、2個目の存在要素を返す (交差)。交差があれば、1個はあるという意味で、any/someになる。


だから、頻繁に使うだろう。emptyとNull合体演算子の上記の記法はいろんなところで頻繁に使うと思われる。基本重要構文。
全部の包含判定したい場合、array_diff ([https://www.php.net/manual/ja/function.array-diff.php PHP: array_diff - Manual]) でできる。
$containsAllValues = !array_diff($search_this, $all);
array_diffはarray_intersectと異なり、1個目の配列要素の内、2個目の不在要素を返す (差分)。なので、空なら全包含となる。非空なら非全包含=some。


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


Null合体演算子はNULLしかカバーしないから、emptyが必要な場面がけっこうある。
ポイントとしては、1個目の要素は要素数が少ない配列を指定したほうが速くなる。判定だけで、速度が重要なら、foreachで見つかったらすぐreturnしたほうが速い。


emptyの反対は、strlen/countあたり。ただし、未定義変数のチェックをしてくれないので、!emptyしたほうがいい。
array_intersectが実行結果とboolが同じ向きなので、これを使うとわかりやすいだろう。
====Variable scope====
=====Array Functions=====
出典: [https://www.php.net/manual/en/language.variables.scope.php PHP: Variable scope - Manual]
======compact======
Ref: [https://www.php.net/manual/ja/function.compact.php PHP: compact - Manual].


関数の外で使用するとグローバルスコープになる。ただし、関数内では暗黙にはグローバル変数は使えない。未定義変数扱いになる。
変数名とその値から、配列を作る。extractの逆。


なお、波括弧のブロックスコープは存在しない。
MVCのViewに複数の値を渡す場合などによく使う。
======extract======
Ref: [https://www.php.net/manual/en/function.extract.php PHP: extract - Manual].


関数内でグローバル変数を参照したければ、関数内でglobalで明示的に使用したいグローバル変数を宣言する必要がある。
配列のキー・バリューを変数として取り込む。
======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相当。非常に重要でよく使う。


あるいは、$GLOBALS配列にグローバル変数が入っているのでこれを使う。
callbackにnullを指定すると、複数の配列のzip (unpack) を行う。


C系言語の感覚だと、波括弧でスコープが作られそうなイメージがあるが、PHPの波括弧はスコープを作らない。あくまで、関数の内部かどうか。
ただ、array_mapのコールバックの引数は通常配列の要素が想定されていて、連想配列のキーにはアクセスできない。


逆にいうと、関数内に定義される関数・クラスも基本グローバル。
それをしたかったら、array_reduceを使う。らしい。


子関数に変数を渡したい場合、引数かグローバル変数しかない。他に隠蔽したり、親関数からスコープを引き継ぎたい場合、無名関数を使うしか無い。
[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]
====Variables From External Sources====
======array_flip======
Ref:
[https://www.php.net/manual/ja/function.array-flip.php PHP: array_flip - Manual]
*[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]
配列のキーと値を反転した配列を返す。元のarrayの値は有効なキーを必要とする。つまり、intかstring。型が違う場合、警告が出て無視される。
*[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フォームの関係がある。重要。
 
====Type declarations/型宣言====
[https://www.php.net/manual/ja/language.types.declarations.php PHP: 型宣言 - Manual]
 
関数の引数、戻り値、クラスのプロパティー (PHP 7.4.0以上) に型を宣言できる。これにより、型を保証でき、その型でなければ、TypeErrorをスローする。
 
関数の戻り値だけ、型の指定箇所がやや特殊で、それ以外は原則変数の直前。関数の戻り値の場合、(): の後に指定する。
function name(): type {}


配列渡しはPHP側の仕様。
===Operators===
[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]
*[https://qiita.com/rana_kualu/items/e15a9b7c12f175380244 【PHP7.4】PHPの新たな演算子??=ってなんぞ? #NULL合体代入演算子 - Qiita]
??=
    // NULL合体代入演算子
    $id ??= getId();
    // これと同じ
    $id = $id ?? getId();
    $id = @$id ?: getId();
    $id = isset($id) ? $id : getId();
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
  <?php
  // 三項演算子の使用例
  function sum($a, $b): float {
$action = (empty($_POST['action'])) ? 'default' : $_POST['action'];
    return $a + $b;
}
   
   
  // 上記は以下の if/else 式と同じです。
  // float が返される点に注意
  if (empty($_POST['action'])) {
  var_dump(sum(1, 2));
  $action = 'default';
} else {
  $action = $_POST['action'];
}
  ?>
  ?>
PHP特有事項として、真ん中を省略できる。その場合、1個目がtrueならそれがそのまま戻る。JavaScriptとかC系言語でも真ん中は省略できない。
nullable な型とシンタックスシュガー


式 <code>expr1 ?: expr3</code> の結果は、expr1 が <code>[https://www.php.net/manual/ja/reserved.constants.php#constant.true true]</code> と同等の場合は expr1、 それ以外の場合は expr3 となります。 この場合、expr1 は一度だけ評価されます。
nullableの場合、型名の前に?を指定する (PHP 7.1.0以上)。?TとT|nullは同じ意味。
 
単一の基本型を宣言した場合、 型の名前の前にクエスチョンマーク (?) を付けることで、nullable であるという印を付けることができます。 よって、?T と T|null は同じ意味です。
 
注意: この文法は、PHP 7.1.0 以降でサポートされており、 PHP 8.0で一般化された union 型がサポートされる前から存在します。


条件演算子のネストはわかりにくいので推奨されない。が、条件演算子の省略形は安定している。false以外の最初の引数を評価する。
PHP 7.4未満などの場合は、しかたないのでアノテーションで対応する。
<?php
====Type juggling====
echo 0 ?: 1 ?: 2 ?: 3, PHP_EOL; //1
[https://www.php.net/manual/ja/language.types.type-juggling.php PHP: 型の相互変換 - Manual]
echo 0 ?: 0 ?: 2 ?: 3, PHP_EOL; //2
echo 0 ?: 0 ?: 0 ?: 3, PHP_EOL; //3
?>
NULL合体演算子はnullの時のデフォルト値になるが、こちらはfalseの場合のデフォルト値設定。意味が違う。未定義変数アクセスをガードできないが、それ以外であれば条件演算子の短縮表記のほうをよく使う。非常に重要。


[https://www.php.net/manual/ja/language.operators.logical.php PHP: 論理演算子 - Manual]
型の相互変換。非常に重要。


elvis演算子と呼ばれることもある模様。
いろいろ方法がある。


It also combines nicely with the ?? operator, which is equivalent to an empty() check (both isset() and `!= false`):
共通なのはキャスト (cast)。<syntaxhighlight lang="php">
$x->y ?? null ?: 'fallback';
<?php
instead of:
$foo = 10;  // $foo は整数です
empty($x->y) ? $x->y : 'fallback'
$bar = (bool) $foo;   // $bar は boolean です
=====Null 合体演算子/Null collapsing operator=====
$fst = "$foo"; // to string.
<?php
?>
// $_GET['user'] を取得します。もし存在しない場合は
</syntaxhighlight>C言語と同じで () を前置する。ただし、少々長い。
// 'nobody' を用います。
$username = $_GET['user'] ?? 'nobody';
// 上のコードは、次のコードと同じ意味です。
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
$var ?? 'value = isset($var) ? $var : 'value';


変数がnullの場合のガードの簡易記法。PHP v7.0.0で追加。非常に便利。
文字列への変換は二重引用符囲などもOK。まあ、キャストだけ覚えておくのがシンプル。
====Error Control/エラー制御演算子@====
====Other====
*[https://www.php.net/manual/ja/language.operators.errorcontrol.php PHP: エラー制御演算子 - Manual]
=====型判定=====
*[https://toku1.jp/programming-php-error-handle-operator/ 【PHP】エラー制御演算子に正しい使い道はあるのか? | とりあえず、いっとく!]
[https://www.php.net/manual/ja/function.gettype.php PHP: gettype - Manual]
式の直前に@を前置すると、その式のエラーメッセージを無視する。


基本的に使わないほうがいい。if文などでガードするより処理が遅い。ただし、Viewなどであまり影響ない場合などは記述がシンプルになるという利点もあるかも。
PHPでの型確認・判定方法がいくつかある。
 
*gettype: 変数の型を文字列で返す。boolean/integer/double/string/array/object/resouce/resource (closed) (PHP v7.2.0以上)/NULL/ unknown type
ただ、やっぱり基本は使わないほうがいい。バグの見落としになる。
*get_class: オブジェクトのクラス名
====Logical/論理演算子====
*get_debug_type: 変数の型名をデバッグしやす形で取得。
[https://www.php.net/manual/ja/language.operators.logical.php PHP: 論理演算子 - Manual]
*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=====


論理積と論理和が、and/orと&&/||で2種類存在する。演算子の優先順位が違う。
* [https://www.php.net/manual/ja/language.types.array.php PHP: 配列 - Manual]
// $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演算子を使うことはできない。同じ論理型同士なら成立はするが。


デフォルト値扱いにしたければ、短縮条件演算子?:や、ヌル合体演算子??を使う。
未定義変数 (undefined variable) の値はNULL。


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


* 関数
未定義変数の検知・制御方法がいくつかある。
** 可変長引数リストと関数呼出 [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])。アンパックと通常名前付き引数がある場合、後のやつで上書き不能で実行時エラー。
*isset ([https://www.php.net/manual/en/function.isset.php PHP: isset - 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の無名関数作成時の別の方法。
*empty ([https://www.php.net/manual/en/function.empty.php PHP: empty - Manual])
* 配列のアンパック: [https://www.php.net/manual/ja/language.types.array.php#language.types.array.unpacking PHP: 配列 - Manual]。array_mergeの代替記法。
* ??: Null 合体演算子/Null collapsing operator
** 数値キー・単純配列はPHP 7.4で導入 ([https://www.php.net/manual/ja/migration74.new-features.php#migration74.new-features.core.unpack-inside-array PHP: 新機能 - Manual])
* ??=: NULL合体代入演算子 PHP v7.4以上。
** 文字キー・連想配列は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])
*@: エラー制御演算子
** アンパックと通常名前付き引数がある場合、後のやつで上書き。
*array_key_exists
** 配列のアンパックは、性能面で問題があり、連想配列がPHP 8.1以上というのがあり、array_mergeを使ったほうがよさそう。
issetとempty、Null合体演算子あたりをメインで使う。特にempty。
* [https://qiita.com/mpyw/items/835050cbb5ad8a4c0710 PHP の ... (3点ドット, Three Dots) の種類,全部言えるかな? #QiitaEngineerFesta2022 - Qiita]
 
emptyは以下相当を実施してくれる。値そのものの評価もするので、値が0で正常なときなど場合によっては困る場合もある。
!(isset($var) && $var)
!isset($var) || $var == false
isset($var) && $varは頻繁に使うことになるだろうから、emptyで短縮できる。
empty($var) ? false : true;
$var ?? false;
emptyとissetは関係が逆に似ているがissetは挙動が違う。


関数と配列の2か所で意味がある。配列の他、Traversableオブジェクトも可能。
「Returns true if var exists and has any value other than null. false otherwise.」なので、変数の値を評価はしない。nullかどうかだけしかみない。emptyとは扱いが違うので注意する。


配列や配列変数の直前に...を前置する (スペースは任意)。
だから、頻繁に使うだろう。emptyとNull合体演算子の上記の記法はいろんなところで頻繁に使うと思われる。基本重要構文。


関数の場合、関数定義時の仮引数と、関数呼出時に使用可能。
ただし、emptyは配列が空の場合もtrueになるので、そこは注意する。配列変数の有無を見たければ、issetを使うしかない。


関数定義時の仮引数で指定すると、その変数が可変長引数を受け入れることを意味する。型宣言はその左に指定可能。これにより、func_get_args()を使わなくてもよくなった。
Null合体演算子はNULLしかカバーしないから、emptyが必要な場面がけっこうある。


関数呼出時に使用すると、引数を展開してくれる。配列のアンパックに近い。
emptyの反対は、strlen/countあたり。ただし、未定義変数のチェックをしてくれないので、!emptyしたほうがいい。
<?php
====Variable scope====
function sum(...$numbers) {
 
    $acc = 0;
===== About =====
    foreach ($numbers as $n) {
出典: [https://www.php.net/manual/en/language.variables.scope.php PHP: Variable scope - Manual]。
        $acc += $n;
 
    }
関数の外で使用するとグローバルスコープになる。ただし、関数内では暗黙にはグローバル変数は使えない。未定義変数扱いになる。
    return $acc;
}
echo sum(1, 2, 3, 4);
?>


関数内でグローバル変数を参照したければ、関数内でglobalで明示的に使用したいグローバル変数を宣言する必要がある。
  <?php
  <?php
  function add($a, $b) {
  $a = 1;
    return $a + $b;
$b = 2;
}
   
   
  echo add(...[1, 2])."\n";
  function Sum()
{
    global $a, $b;
   
   
$a = [1, 2];
    $b = $a + $b;
echo add(...$a);
  }
  ?>
あるいは、$GLOBALS配列にグローバル変数が入っているのでこれを使う。


<?php
なお、'''波括弧のブロックスコープは存在しない'''。C系言語の感覚だと、波括弧でスコープが作られそうなイメージがあるが、PHPの波括弧はスコープを作らない。あくまで、関数の内部かどうか。
function total_intervals($unit, DateInterval ...$intervals) {
    $time = 0;
    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:に注意。
子関数に変数を渡したい場合、引数かグローバル変数しかない。他に隠蔽したり、親関数からスコープを引き継ぎたい場合、無名関数を使うしか無い。


この構文は存在だけ知っておくだけでいいと思われる。
===== Super global =====
====elseif/else if====
[https://www.php.net/manual/ja/language.variables.superglobals.php PHP: スーパーグローバル - Manual]
[https://www.php.net/manual/ja/control-structures.elseif.php PHP: elseif/else if - Manual]


1単語で書ける。結果は同じだが、文法的な意味が異なる。
全てのスコープで使用可能な組込変数。関数、メソッド内でもglobal $variable;とする必要がない。
====for/foreach====
foreachは配列の反復処理のための制御構造。
foreach(iterable_expression as $value)
foreach(iterable_expression as $key => $value)
$keyも使いたい場合、2番目の形式を使う。


ループ中に$valueの要素を直接変更したい場合、&をつけておく。
* $GLOBALS: グローバル変数の連想配列。
foreach(iterable_expression as &$value)
* $_SERVER
====declare====
* $_GET
Source: [https://www.php.net/manual/en/control-structures.declare.php PHP: declare - Manual].
* $_POST
* $_FILES
* $_COOKIE
* $_SESSION
* $_REQUEST
* $_ENV
 
====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フォームの関係がある。重要。


PHPUnitのサンプルコード ([https://phpunit.de/getting-started/phpunit-9.html Getting Started with Version 9 of PHPUnit – The PHP Testing Framework]) などで冒頭に以下の記述がある。
配列渡しはPHP側の仕様。
<?php declare(strict_types=1);
これの意味が分かっていなかったので整理する。


declare文 (construct) は、コードブロックの実行指令となる。以下の構文となる。
=== Constants ===
declare (<directive>)
  <statement>
<directive> はdeclareブロックの挙動を指示する。指定可能なものは以下3個だ。
#ticks
#encoding
#strict_types: =1の指定でPHPの暗黙の型変換を無効にする (ストリクトモード)。ただし、影響するのはスカラー型のみ。型が違う場合、TypeErrorの例外が発生する。
指令はファイルコンパイル時に処理されるので、リテラル値のみが使用可能で、変数や定数は使用不能。


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


declare文はグローバルスコープで使われる。登場以後のコードに影響する。ただし、他のファイルからincludeされても、親ファイルには影響しない。だから安心して使える。
* [https://www.php.net/manual/ja/language.constants.php PHP: 定数 - Manual]
* [https://www.php.net/manual/ja/function.define.php PHP: define - Manual]


型安全にするために、基本的にPHPファイルの冒頭に<code>declare(strict_types=1);</code>を書いておいたほうがよいだろう。
定数は値のためのID (名前)。基本的にスクリプト実行中に変更できない。大文字小文字を区別するが、慣習として大文字で表記する。
===Function===
=====Return value=====
Ref: [https://www.php.net/manual/ja/functions.returning-values.php PHP: 戻り値 - Manual].


関数はreturn文で値を返せる。そこで処理を終了する。
constキーワードか、define関数で定義できる。constの場合、制約がある。


returnを省略した場合、nullを返す。
constで指定可能なのは、スカラー式 (bool/int/float/string) と、スカラー式のみのarray。動的な設定はできない。


==== Variable Functions/可変関数/Callable/コールバック ====
変数と異なり、$の前置は不要。


* [https://www.php.net/manual/ja/functions.variable-functions.php PHP: 可変関数 - Manual]
定数の定義判定は、defined()を使う。
* [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という型がある。
* $不要。
* スコープに関係なく、あらゆる場所からアクセス可能。
* 後から再定義、未定義不能。
* スカラー値と配列のみ。


可変関数は、関数名の文字列の変数に丸括弧を追加したら実行できるというもの。インスタンス変数があれば、メソッドもできる。
constはコンパイル時に定義されるため、トップレベル以外、つまりブロック内部 (関数/ループ/if/try) で宣言できない。defineはできる。


PHP 7.0から、関数のみ"str"()も可能になった。「[https://www.php.net/manual/ja/migration70.php PHP: PHP 5.6.x から PHP 7.0.x への移行 - Manual]」に記載はないが、パース方法が変わったことが由来の模様。
==== define/const ====


関数もメソッドも統一的に扱うものとして、Callable型がある。
* [https://qiita.com/schrosis/items/485b984e05b2eb4521b4 PHPの「define」と「const」の違い #定数 - Qiita]
* [https://qiita.com/nishimura/items/a396c999a85fa4cbc4a0 PHPでプログラム全体の設定に使う変数の保持の仕方 #PHP - Qiita]


CallableはPHPで関数を引数として渡したり、関数名の文字列を渡して、動的に関数を実行する仕組み。
{| class="wikitable"
|+
!項目
!const
!define
|-
|構文
|予約語 (少し速い)
|関数
|-
|戻り値
|なし
|あり
|-
|定義元
|スカラー値のみ
|変数/関数OK
|-
|クラス定数
|x
| -
|-
|使用箇所
|制御ブロック内部以外
|どこでも
|-
|スコープ
|名前空間
|グローバル
|}
defineはブロック内で使えるので、何らかの条件で定義を変更できるのが利点。例えば、環境を本番とデバッグに変えたりなど。


callable型で表す。関数だけでなく、メソッドやstaticメソッドも対応できる。方法が2種類ある。
動的に変更したいならdefine、それ以外は名前空間やクラス定数として使えるconstだろうか。関数内のマジックナンバー的な使い方はできない。そういうのは、普通の変数で取り扱う。


# 関数: 関数名の文字列。
ただ、constはアプリの設定として使うことはない。クラスの固有値の定義。
# メソッド: 配列で指定。0番目の要素に、インスタンスやオブジェクト。1番目の要素にメソッド名の文字列で指定する。
# staticメソッド:  配列で指定。0番目の要素に、クラス名を指定する。'ClassName::methodName' 形式でも指定可能。


=====anonymous/無名関数=====
==== constant ====
callableの型。非常に重要。
[https://www.php.net/manual/ja/function.constant.php PHP: constant - Manual]
// "use" がない場合
$example = function () {
    var_dump($message);
};
$example();
// $message を引き継ぎます
$example = function () use ($message) {
    var_dump($message);
};
useを指定した場合だけ、親のスコープから変数を引き継げる。変数は関数定義時の値。


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


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


===Classes and Objects===
==== class ====
====The Basics====
クラス内に定数を定義できる。デフォルトでpublic。staic変数的な扱い。インスタンスではなく、クラスが保有する。
Ref: [https://www.php.net/manual/en/language.oop5.basic.php PHP: The Basics - Manual].
=====class=====
class内には変数 (プロパティー)、定数、関数 (メソッド) を含められる。


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


C系言語であれば、$this->相当は省略できたが、PHPでは指定が必要なので注意する。
* [https://www.php.net/manual/ja/language.constants.predefined.php PHP: 自動的に定義される定数 - Manual]
=====::class=====
* [https://www.php.net/manual/ja/reserved.constants.php PHP: 定義済みの定数 - Manual]
<className>::classでクラス名の完全修飾子の文字列を取得できる。


例外の試験など、クラス名の情報が必要な時によくみかける。
言語で定義済みの定数がいろいろある。true/false/nullなど。


PHP 8.0.0からオブジェクトに対しても::classを使用でき、元のクラス名を取得できる。その場合、get_class()と同じ。同じならPHP 7で使えないのでget_class()でいいか。
==== Magic/マジック定数 ====
====Property====
使用箇所で値が変化する定数 (マジック定数) が9個ある。C言語のマクロに近い。コンパイル時に解決される。大文字小文字を区別しない。
Ref: [https://www.php.net/manual/ja/language.oop5.properties.php PHP: プロパティ - Manual].
{| 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>
|完全に修飾されたクラス名。
|}
どれもよく使う。


クラスのメンバー変数のことをプロパティー (property) とPHPでは呼んでいる。
===Operators===
 
[https://www.php.net/manual/en/language.operators.php PHP: Operators - Manual]
クラス内で、1以上のキーワード (アクセス権、static、PHP 8.1.0以後のみreadonly) のあとに、オプション型宣言 (PHP 7.4以後、readonly以外) の後に変数宣言を続ける。
====Assignment/代入演算子====
public $var1
*[https://www.php.net/manual/en/language.operators.assignment.php PHP: Assignment - Manual]
static $var2
*[https://qiita.com/rana_kualu/items/e15a9b7c12f175380244 【PHP7.4】PHPの新たな演算子??=ってなんぞ? #NULL合体代入演算子 - Qiita]
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])。
    // NULL合体代入演算子
 
    $id ??= getId();
宣言時に初期値を代入もできるが、初期値は定数のみ。関数類は使用不能。
   
*[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]
    $id = $id ?? getId();
以下のエラーが出る。
    $id = @$id ?: getId();
  PHP Fatal error: Constant expression contains invalid operations in /ぼくのかんがえたさいきょうのクラス.php on line 5
    $id = isset($id) ? $id : getId();
関数類で動的に代入したい場合、__constructでやる。
NULL合体演算子の代入版。nullの場合の代入が簡単になった。PHP 7.4から使用可能。
====Autoloading Classes====
====Comparison/比較演算子====
Ref:
*[https://www.php.net/manual/ja/language.operators.comparison.php PHP: 比較演算子 - Manual]
*[https://www.php.net/manual/en/language.oop5.autoload.php PHP: Autoloading Classes - Manual]
*[https://www.php.net/manual/ja/migration70.new-features.php PHP: 新機能 - Manual]
*[https://www.php.net/manual/en/function.spl-autoload-register.php PHP: spl_autoload_register - Manual]
=====三項演算子 (条件演算子)=====
別のファイルのクラスを使う方法の話。
if/elseの短縮表記。デフォルト値の設定などでよく使う。
#require_once()/require()/include: シンプルなファイル読み込み。PHP 4から。
<?php
#__autoload(): 非推奨。PHP 5.0で登場。
// 三項演算子の使用例
#spl_autoload_register(): PHP標準。PHP 5.1.0で登場。
$action = (empty($_POST['action'])) ? 'default' : $_POST['action'];
#composer autoload: composer。
C系言語であれば、includeなどで外部ファイルをそのまま自分のファイルに読み込む。PHPでもrequire_onceなどで似たようなこともできる。が、PHPではこれをクラスごとに記述するのが煩雑だとして、自動で読み込む仕組みがいくつかある。
// 上記は以下の if/else 式と同じです。
if (empty($_POST['action'])) {
  $action = 'default';
} else {
  $action = $_POST['action'];
}
?>
PHP特有事項として、真ん中を省略できる。その場合、1個目がtrueならそれがそのまま戻る。JavaScriptとかC系言語でも真ん中は省略できない。


GNU socialでも <https://notabug.org/gnusocialjp/gnusocial/src/main/lib/util/framework.php> でspl_autoload_registerを使っている。
<code>expr1 ?: expr3</code> の結果は、expr1 が <code>[https://www.php.net/manual/ja/reserved.constants.php#constant.true true]</code> と同等の場合は expr1、 それ以外の場合は expr3 となります。 この場合、expr1 は一度だけ評価されます。


基本的にはcomposerのautoloadかPHP標準のspl_autoload_registerの2択になっている。
条件演算子のネストはわかりにくいので推奨されない。が、条件演算子の省略形は安定している。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の場合のデフォルト値設定。意味が違う。未定義変数アクセスをガードできないが、それ以外であれば条件演算子の短縮表記のほうをよく使う。非常に重要。


基本的な使用方法。<syntaxhighlight lang="php">
Null合体演算子を組み合わせて、未定義のなどの場合のデフォルト値設定で役立つ。
<?php
spl_autoload_register(function ($class_name) {
    include $class_name . '.php';
});


$obj  = new MyClass1();
[https://www.php.net/manual/ja/language.operators.logical.php PHP: 論理演算子 - Manual]
$obj2 = new MyClass2();
?>


</syntaxhighlight>MyClass1.php MyClass2.phpから該当クラスを自動読み込みする。
elvis演算子と呼ばれることもある模様。


該当クラスを使おうとしたときに、spl_autoload_registerに登録した関数が呼ばれる模様。
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';


spl_autoload_registerは、指定した関数を__autoload()の実装として登録する。順番に登録する。
変数がnullの場合のガードの簡易記法。PHP v7.0.0で追加。非常に便利。
spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): bool
====Error Control/エラー制御演算子@====
callback: <code>callback(string $class): void</code> 。重要。nullを指定するとデフォルトのspl_autload()が登録される。$classにはクラスの完全修飾子が入る。
*[https://www.php.net/manual/ja/language.operators.errorcontrol.php PHP: エラー制御演算子 - Manual]
*[https://toku1.jp/programming-php-error-handle-operator/ 【PHP】エラー制御演算子に正しい使い道はあるのか? | とりあえず、いっとく!]
式の直前に@を前置すると、その式のエラーメッセージを無視する。


このcallback内で独自のrequire_once相当をいろいろ指定する形になる。
基本的に使わないほうがいい。if文などでガードするより処理が遅い。ただし、Viewなどであまり影響ない場合などは記述がシンプルになるという利点もあるかも。
====Traits====
 
[https://www.php.net/manual/ja/language.oop5.traits.php PHP: トレイト - Manual]
ただ、やっぱり基本は使わないほうがいい。バグの見落としになる。
====Logical/論理演算子====
[https://www.php.net/manual/ja/language.operators.logical.php PHP: 論理演算子 - Manual]


コード再利用のための仕組み。単一継承言語で、コードを再利用するための仕組み。関数クラス (デリゲート) 的なもの。クラスに関数クラスのメソッドを取り込める。インスタンス生成などはできず、関数を水平方向で構成可能にする。継承しなくても、メンバーに追加できる。
論理積と論理和が、and/orと&&/||で2種類存在する。演算子の優先順位が違う。
<?php
// $g に代入されるのは、(true && false) の評価結果です
  trait ezcReflectionReturnInfo {
  // これは、次の式と同様です: ($g = (true && false))
    function getReturnType() { /*1*/ }
  $g = true && false;
    function getReturnDescription() { /*2*/ }
  }
   
   
  class ezcReflectionMethod extends ReflectionMethod {
  // $h に true を代入してから "and" 演算子を評価します
    use ezcReflectionReturnInfo;
  // これは、次の式と同様です: (($h = true) and false)
    /* ... */
  $h = true and false;
  }
なお、PHPの論理演算子は、常に論理値 (true/false) を返すので注意する。
$a = $var || 'default';
class ezcReflectionFunction extends ReflectionFunction {
上記のように、デフォルト値の代入扱いでor演算子を使うことはできない。同じ論理型同士なら成立はするが。
    use ezcReflectionReturnInfo;
 
    /* ... */
デフォルト値扱いにしたければ、短縮条件演算子?:や、ヌル合体演算子??を使う。
}
?>
====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()
==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)) {
** 可変長引数リストと関数呼出 [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])。アンパックと通常名前付き引数がある場合、後のやつで上書き不能で実行時エラー。
    echo "File is valid, and was successfully uploaded.\n";
** 第一級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の無名関数作成時の別の方法。
} else {
* 配列のアンパック: [https://www.php.net/manual/ja/language.types.array.php#language.types.array.unpacking PHP: 配列 - Manual]。array_mergeの代替記法。
    echo "Possible file upload attack!\n";
** 数値キー・単純配列は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]


echo 'Here is some more debugging info:';
関数と配列の2か所で意味がある。配列の他、Traversableオブジェクトも可能。
print_r($_FILES);


print "</pre>";
配列や配列変数の直前に...を前置する (スペースは任意)。


?>
関数の場合、関数定義時の仮引数と、関数呼出時に使用可能。
</syntaxhighlight>上記がイメージ。


DBに保存する場合は「[https://qiita.com/NULLchar/items/7bdc6685be0aa909e8fe PHPとMySQLを利用した画像・動画のアップロード・保存・表示 #PHP - Qiita]」も参考になる。
関数定義時の仮引数で指定すると、その変数が可変長引数を受け入れることを意味する。型宣言はその左に指定可能。これにより、func_get_args()を使わなくてもよくなった。
===Using PHP from the command line===
Ref: [https://www.php.net/manual/en/features.commandline.php PHP: Command line usage - Manual].


簡単なコードの確認などでPHPをコマンドラインなどから簡単に実行したいことがよくある。いくつか方法がある ([https://www.php.net/manual/en/features.commandline.usage.php PHP: Usage - Manual])
関数呼出時に使用すると、引数を展開してくれる。配列のアンパックに近い。
#phpコマンドの引数にファイルを指定: <code>php file.php</code>/<code>php -f file.php</code>
<?php
#phpコマンドの引数にコードを指定: <code>php -r 'print_r(get_defined_constants());'</code>
function sum(...$numbers) {
#phpコマンドに標準入力で読み込み: <code>php <file.php</code>
    $acc = 0;
標準入力が一番使いやすく感じる。
    foreach ($numbers as $n) {
==Function Reference==
        $acc += $n;
===Affecting PHP's Behavior===
    }
====Error Handling====
    return $acc;
=====Runtime Configuration=====
}
PHPのエラー設定を整理する。 PHPのエラー設定は「[https://www.php.net/manual/en/errorfunc.configuration.php PHP: Runtime Configuration - Manual]」で一覧化されている。
echo sum(1, 2, 3, 4);
?>


xmlrpc_errors, syslog.facility, syslog.ident以外はどこでも設定可能。
<?php
 
function add($a, $b) {
特に重要なのが以下の設定。
    return $a + $b;
{| class="wikitable"
}
!設定
!初期値
echo add(...[1, 2])."\n";
!説明
|-
$a = [1, 2];
|error_reporting
echo add(...$a);
|<code>E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED</code>
?>
|エラー出力レベルを設定する。開発時にはE_ALL (2147483647/-1) にしておくとよい。
 
|-
<?php
|display_errors
function total_intervals($unit, DateInterval ...$intervals) {
|"1"
    $time = 0;
|エラーのHTML出力への表示を設定する。"stderr"を指定すると,stderrに送る。デフォルトで有効なのでそのままでいい。
    foreach ($intervals as $interval) {
|-
        $time += $interval->$unit;
|display_startup_errors
    }
|"0"
    return $time;
|PHPの起動シーケンス中のエラー表示を設定する。デバッグ時は有効にしておいたほうがいい。
}
|-
[https://qiita.com/shigakin/items/5e4a2784bbd6b227f4d3 【PHP8.1】あなたはどっち? array_merge VS unpacking(スプレッド演算子) #PHP - Qiita]
|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


; For file
なお、配列のアンパックに関しては、array_mergeのほうが速くてメモリーも少ないとのこと。
display_errors = stderr
===Control Structures===
</syntaxhighlight>htpd.conf/.htaccessの場合は以下。<syntaxhighlight lang="http">
Source: [https://www.php.net/manual/en/language.control-structures.php PHP: Control Structures - Manual].
php_value error_reporting -1
====制御構造に関する別の構文====
php_flag display_startup_errors on
[https://www.php.net/manual/ja/control-structures.alternative-syntax.php PHP: 制御構造に関する別の構文 - Manual]
php_flag log_errors on


# For file
if、 while、for、 foreach、switch に関する別の構文がある。開き波括弧部分を:に、閉じ波括弧部分をendif;,endwhile;, endfor;,endforeach;, endswitch;などにできる。else:とelseif:に注意。
php_value display_errors stderr
</syntaxhighlight>
===File system===
Ref: [https://www.php.net/manual/ja/refs.fileprocess.file.php PHP: ファイルシステム - Manual].
====File system====
=====io=====
*[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] — データをファイルに書き込む
*[https://www.php.net/manual/ja/function.file.php PHP: file - Manual]: ファイル全体を読み込んで改行区切りで配列にする。
上記2個の非常に重要な入出力関数がある。


バイナリーやHTTP GETに対応している。アップロードされたファイルの読み込みなどでお世話になる。
この構文は存在だけ知っておくだけでいいと思われる。
=====check=====
====elseif/else if====
*[https://www.php.net/manual/ja/function.file-exists.php PHP: file_exists - Manual]
[https://www.php.net/manual/ja/control-structures.elseif.php PHP: elseif/else if - 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]
入出力とセットで使うファイルの不在確認の関数群。
===International===
====mbstring====
=====mb_substr=====
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,
1単語で書ける。結果は同じだが、文法的な意味が異なる。
====for/foreach====
foreachは配列の反復処理のための制御構造。
foreach(iterable_expression as $value)
foreach(iterable_expression as $key => $value)
$keyも使いたい場合、2番目の形式を使う。


    int $start,
ループ中に$valueの要素を直接変更したい場合、&をつけておく。
foreach(iterable_expression as &$value)
====declare====
Source: [https://www.php.net/manual/en/control-structures.declare.php PHP: declare - Manual].


    ?int $length = null,
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);
これの意味が分かっていなかったので整理する。


    ?string $encoding = null
declare文 (construct) は、コードブロックの実行指令となる。以下の構文となる。
declare (<directive>)
  <statement>
<directive> はdeclareブロックの挙動を指示する。指定可能なものは以下3個だ。
#ticks
#encoding
#strict_types: =1の指定でPHPの暗黙の型変換を無効にする (ストリクトモード)。ただし、影響するのはスカラー型のみ。型が違う場合、TypeErrorの例外が発生する。
指令はファイルコンパイル時に処理されるので、リテラル値のみが使用可能で、変数や定数は使用不能。


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


substr同様、lengthにはマイナス値を指定可能。その場合、末尾からの文字数になる。
declare文はグローバルスコープで使われる。登場以後のコードに影響する。ただし、他のファイルからincludeされても、親ファイルには影響しない。だから安心して使える。


省略するかnullを指定すると、全文字。0は0文字。
型安全にするために、基本的にPHPファイルの冒頭に<code>declare(strict_types=1);</code>を書いておいたほうがよいだろう。
===Text===
 
====Strings====
==== return ====
=====strpos=====
[https://www.php.net/manual/ja/function.return.php PHP: return - Manual]
Ref: [https://www.php.net/manual/ja/function.strpos.php PHP: strpos - 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。


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]]
関数定義など、絶対必要なものはrequireなど。


文字列 <code>haystack</code> の中で、 <code>needle</code> が最初に現れる位置を探します。
===== _once =====
読込済みなら、再読込しない点がinclude/requireとの決定的な違い。関数の複数定義のエラーを回避できたりする。


include相当。よく使う。
読み込めたらtrueを返す。


戻り値に注意。有無の確認時は、strpos() !== falseの厳密一致でチェックする必要がある。
===== config.php =====


===== echo/print/printf =====
* [https://stackoverflow.com/questions/14752470/creating-a-config-file-in-php Creating a config file in PHP - Stack Overflow]
* [https://blog.websandbag.com/entry/2018/12/04/160218 【PHP】静的なconfigファイルの書き方 - websandbag ブログ]
 
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をアプリ内で作りたい場合、「[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を使うとよい。
// create the array as a php text string
$text = "<?php\n\nreturn " . var_export($myarray, true) . ";";
 
===== config class =====
config.phpをどう用意するかは議論がある。
 
* [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]
* [https://qiita.com/nishimura/items/a396c999a85fa4cbc4a0 PHPでプログラム全体の設定に使う変数の保持の仕方 #PHP - Qiita]
* [https://php-archive.net/php/config-class/ [PHP]コンフィグファイルから設定情報を読み込むためのConfigクラス | PHP Archive]
* [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定数にするという。
 
* クラスのconst定数
* iniファイル/parse_ini_file
 
他に、configクラスを用意しておいて、シングルトンか、staticメソッドで参照する形。
 
どれくらいの頻度で参照するか次第。参照頻度が低いなら、getで毎回設定ファイルを読み込む。多いならstaticのクラス変数にもたせる。
===Function===
 
==== User defined ====
[https://www.php.net/manual/ja/functions.user-defined.php 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: [https://www.php.net/manual/ja/functions.returning-values.php PHP: 戻り値 - Manual].
 
関数はreturn文で値を返せる。そこで処理を終了する。
 
returnを省略した場合、nullを返す。
 
==== Variable Functions/可変関数/Callable/コールバック ====
 
* [https://www.php.net/manual/ja/functions.variable-functions.php PHP: 可変関数 - Manual]
* [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を指定した場合だけ、親のスコープから変数を引き継げる。変数は関数定義時の値。
 
 
 
===Classes and Objects===
====The Basics====
Ref: [https://www.php.net/manual/en/language.oop5.basic.php 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: [https://www.php.net/manual/ja/language.oop5.properties.php 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と同じ意味になる ([https://stackoverflow.com/questions/1206105/what-does-php-keyword-var-do What does PHP keyword 'var' do? - Stack Overflow])。
 
宣言時に初期値を代入もできるが、初期値は定数のみ。関数類は使用不能。
*[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ではこれをクラスごとに記述するのが煩雑だとして、自動で読み込む仕組みがいくつかある。
 
GNU socialでも <https://notabug.org/gnusocialjp/gnusocial/src/main/lib/util/framework.php> でspl_autoload_registerを使っている。
 
基本的にはcomposerのautoloadかPHP標準のspl_autoload_registerの2択になっている。
 
基本的な使用方法。<syntaxhighlight lang="php">
<?php
spl_autoload_register(function ($class_name) {
    include $class_name . '.php';
});
 
$obj  = new MyClass1();
$obj2 = new MyClass2();
?>
 
</syntaxhighlight>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: <code>callback(string $class): void</code> 。重要。nullを指定するとデフォルトのspl_autload()が登録される。$classにはクラスの完全修飾子が入る。
 
このcallback内で独自のrequire_once相当をいろいろ指定する形になる。
 
==== スコープ定義演算子 (::) ====
 
* [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]
 
スコープ定義演算子 (::) はトークンの一つ。定数、staticプロパティー、staticメソッド、親クラスなどにアクセスできる。
 
[Paamayim Nekudotayim] とも呼ぶ。ダブルコロンを意味するヘブライ語らしい。
 
staticメソッド/プロパティーは、遅延静的束縛 (Late Static Bindings) でアクセス可能。
 
* 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]
 
コード再利用のための仕組み。単一継承言語で、コードを再利用するための仕組み。関数クラス (デリゲート) 的なもの。クラスに関数クラスのメソッドを取り込める。インスタンス生成などはできず、関数を水平方向で構成可能にする。継承しなくても、メンバーに追加できる。
<?php
trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}
?>
====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()
 
=== Reserved ===
 
==== keywords ====
[https://www.php.net/manual/ja/reserved.keywords.php PHP: キーワードのリスト - Manual]
 
式や関数ではなく、定数、クラス名、関数名として使えず、PHPで予約されている特別なキーワードがいくつかある。
 
statement/文に近い扱い。言語構文の一部扱い。
{| 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__]]
|
|
|
|
|}
 
==== Interfaces ====
[https://www.php.net/manual/ja/reserved.interfaces.php PHP: 定義済みのインターフェイスとクラス - Manual]
 
===== stdClass =====
[https://www.php.net/manual/ja/class.stdclass.php PHP: stdClass - Manual]
 
動的なプロパティーが使える、汎用的な空クラス。このクラス自体は、メソッドやプロパティーを持たない。
 
json_decodeなど一部の関数がこのインスタンスを返す。
// 型変換での作成。連想配列を(object)にキャストすると作れる。
(object) array('foo' => 'bar');
データベースの取得結果が、連想配列の他に、stdClassになっていることがある。
 
匿名オブジェクトや、動的プロパティーなどが主な利用方法。
 
データホルダーとして使う場合、連想配列のキーのほうが、自由度が高いので、そちらのほうが便利だと思われる。たくさんある配列関数も使えるし。
 
==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>上記がイメージ。
 
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].
 
簡単なコードの確認などでPHPをコマンドラインなどから簡単に実行したいことがよくある。いくつか方法がある ([https://www.php.net/manual/en/features.commandline.usage.php PHP: Usage - Manual])。
#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]
 
=====Runtime Configuration=====
PHPのエラー設定を整理する。 PHPのエラー設定は「[https://www.php.net/manual/en/errorfunc.configuration.php PHP: Runtime Configuration - Manual]」で一覧化されている。
 
xmlrpc_errors, syslog.facility, syslog.ident以外はどこでも設定可能。
 
特に重要なのが以下の設定。
{| 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
 
; 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
 
# For file
php_value display_errors stderr
</syntaxhighlight>
 
===== logrotate =====
 
* ライブラリー
** [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制作会社 グローワークス]
 
出力したログファイルがストレージを圧迫しないように、一定サイズ・期間でリネームして、最大保持数を維持したりする。
 
いくつか方法がある。
 
* GNU/Linuxのlogrotateコマンド
* logrotateライブラリー
* 自前実装
 
やることは決まっているのだから、自前で実装してもいいかもしれない。
 
# ログ出力時
# ログ出力ファイルのサイズを確認して、設定サイズより大きければ、循環。
# 最古のログファイルを削除して、順番にリネーム。
# 最後に出力。
 
それだけ。
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);
}
もう少しいい実装方法はありそう。
 
=== 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) の拡張のモジュールと関係する関数とのこと。
 
両方とも、引数に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: [https://www.php.net/manual/ja/refs.fileprocess.file.php PHP: ファイルシステム - Manual].
====File system====
=====io=====
*[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に対応している。アップロードされたファイルの読み込みなどでお世話になる。
=====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なら\、それ以外/らしい。
 
===== ディレクトリー以下のファイル一覧 =====
 
* [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
 
ソートや簡易検索が必要かどうかで、速度や適切な方法が異なる。
 
ただ、使いやすいのは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:
*[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の厳密一致でチェックする必要がある。
 
===== echo/print/printf =====
 
* [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の違いとは?どっちを使えば良い?]
 
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 =====
 
* [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]
 
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 '<nowiki><pre>';</nowiki>
        print_r($val);
        echo  '<nowiki></pre></nowiki>';
}
?>
 
===== var_exportの変数取込 =====
var_exportの文字列表現。config.phpに出力して、Includeするほかに、テキストを変数にしたいことがある。
 
* [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]
* [https://tips.recatnap.info/laboratory/detail/id/575 reCatnap: php var_export()した後の文字列を元に戻す(eval())]
 
素直にevalする。
$dumpStr = var_export($var,true);
eval("$somevar = $dumpStr;");
くれぐれも入力に注意する。
 
=== 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をよく使う。非常に重要。
 
json_encodeはオブジェクトをJSON文字列表記にできるのでデバッグなどで便利。
 
[https://kohkimakimoto.hatenablog.com/entry/2012/05/17/180738 PHPでUnicodeアンエスケープしたJSONを出力する関数 - オープンソースこねこね]
 
JSON_UNESCAPED_UNICODE をオプションに指定しないと日本語はユニコードエスケープ表記になる。


* [https://www.php.net/manual/ja/function.echo.php PHP: echo - Manual]
===== 連想配列 =====
* [https://www.php.net/manual/ja/function.print.php PHP: print - Manual]
json_decode(
* [https://www.php.net/manual/ja/function.printf.php PHP: printf - Manual]
    string $json,
* [https://qiita.com/tadsan/items/e09475093bc336881b20 echoとprintの違い #PHP - Qiita]
    ?bool $associative = null,
* [https://qiita.com/chimayu/items/dcc443c3a99f3379b3da PHP : echoとprintの違い #まとめ - Qiita]
    int $depth = 512,
* [https://inouelog.com/php-echo-print/ 【PHP】echoとprintの違いとは?どっちを使えば良い?]
    int $flags = 0
): mixed
json_decodeは第二引数にtrueを指定しないと、object (stdClass) になる。trueにすると、連想配列になる。基本は連想配列でいいと思う。


PHPの出力関数群。よく使うが、扱いが特殊なので整理する。
==== Other ====


まず、echo/printは関数ではなく、if/forなどと同じ言語構造、キーワード扱い。丸括弧はなくてもいい。紛らわしいのでないほうがいい。
===== eval =====
[https://www.php.net/manual/ja/function.eval.php PHP: eval - Manual]


* 共通: 末尾に改行は付与されない。自分で"\n"を指定必要。関数ではないので丸括弧は不要。
文字列をPHPコードとして評価する。危険なので、特にユーザーから入力を受け付ける場合は、注意する。できれば使わないほうがいい。
* echo: 戻り値void。式ではないので、if returnなどで使えない。戻り値がない分printよりわずかに速い。文字数が短い。コンマ区切りで複数列挙可能。文字列連結するよりコンマ区切りのほうが.演算子の優先順位など扱いが簡単。HTMLでの埋め込みに便利な <?= ?>の短縮表記 (<?php echo ; ?>相当)もあり。
* print: 戻り値intで常に1を返す。ifや条件演算子の結果部分など、式の文脈で使用可能。


基本はechoでいい。if return/条件演算子など戻り値や式が必要な箇所でだけprintを使う。
evalで評価する文字列内でreturnした結果が返却値となる。ないならnull。戻り値が必要なら、テキスト内で忘れずにreturnする。


printfは関数。書式指定が必要ならこれ。
===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]
json_encode/json_decodeをよく使う。非常に重要。
json_encodeはオブジェクトをJSON文字列表記にできるのでデバッグなどで便利。
[https://kohkimakimoto.hatenablog.com/entry/2012/05/17/180738 PHPでUnicodeアンエスケープしたJSONを出力する関数 - オープンソースこねこね]
JSON_UNESCAPED_UNICODE をオプションに指定しないと日本語はユニコードエスケープ表記になる。
==Topic==
==Topic==
===HTTP===
===HTTP===

2024年9月6日 (金) 17:40時点における版

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;
    /* ... */
}
?>

Other

クラス名の取得
get_class($object);
クラス名::class
$object::class // PHP 8.0以上 (get_class相当)
(new \ReflectionClass($obj))->getShortName();

基本は名前空間付きのフルパスでの取得。クラス名だけだとgetShortName()

Reserved

keywords

PHP: キーワードのリスト - Manual

式や関数ではなく、定数、クラス名、関数名として使えず、PHPで予約されている特別なキーワードがいくつかある。

statement/文に近い扱い。言語構文の一部扱い。

* readonly は、関数名として使用できます。

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

もう少しいい実装方法はありそう。

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

io
  • file_get_contents — ファイルの内容を全て文字列に読み込む
  • file_put_contents: データをファイルに書き込む。戻り値に書き込みバイト数を返す。失敗したらfalse。成否は完全一致===falseで。
  • PHP: file - Manual: ファイル全体を読み込んで改行区切りで配列にする。

上記2個の非常に重要な入出力関数がある。

バイナリーやHTTP GETに対応している。アップロードされたファイルの読み込みなどでお世話になる。

check

入出力とセットで使うファイルの不在確認の関数群。

path

パス関係の操作。重要。

Directory

Constants

PHP: 定義済み定数 - Manual

  • DIRECTORY_SEPARATOR: Windowsなら\、それ以外/らしい。
ディレクトリー以下のファイル一覧

方法がいくつかある。

  • 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

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