PHP 8.4は2024年11月21日にリリースされたバージョンで、プロパティフックや非対称プロパティ可視性といった構文レベルの追加が入りました。ただし2026年8月現在、8.4のアクティブサポート終了は2026年12月31日に迫っています。本記事では8.4で何が変わったのかを、PHP 8.4.24(CLI)で実際に実行した結果とともに整理し、8.3からの移行で先に直すべき非推奨、そして今から8.4を選ぶべきかどうかまでを扱います。
まとめ:PHP 8.4で押さえる6点
- 8.4.0のリリースは2024年11月21日。アクティブサポートは2026年12月31日まで、セキュリティ修正は2028年12月31日まで。
- 構文の目玉はプロパティフック(get/setのみ、isset/unsetフックは存在しない)と非対称プロパティ可視性(
public private(set))。 - 遅延オブジェクトは
ReflectionClass::newLazyGhost()とnewLazyProxy()の2方式で、コンストラクタの実行をプロパティに触れるまで先送りできる。 - 配列検索は
array_find()・array_find_key()・array_any()・array_all()の4関数が追加。空配列に対するarray_all()はtrueを返す。 - 移行で最初に踏むのは暗黙のnullable型の非推奨。
string $x = nullは?string $x = nullへ書き換える。 - 新規プロジェクトなら8.4で止めず8.5を選ぶ。PHP 8.5は2025年11月20日リリースで、アクティブサポートが2027年12月31日まで残っている(PHP 8.5の変更点と新機能まとめ)。
以下、各機能の実際の書き方と、実行して確認した出力を順に見ていきます。
PHP 8.4のサポート期限と最新パッチ
新機能より先に確認したいのが、そのバージョンをいつまで使えるかです。php.netのサポート状況ページに記載されている日付は次のとおりです。
| バージョン | 初回リリース | アクティブサポート終了 | セキュリティ修正終了 |
|---|---|---|---|
| PHP 8.3 | 2023年11月23日 | 2025年12月31日(終了済み) | 2027年12月31日 |
| PHP 8.4 | 2024年11月21日 | 2026年12月31日 | 2028年12月31日 |
| PHP 8.5 | 2025年11月20日 | 2027年12月31日 | 2029年12月31日 |
アクティブサポート期間中はバグ修正とセキュリティ修正の両方が入り、それ以降はセキュリティ修正のみになります。8.4系のパッチリリースは4週ごとに続いています。2026年8月20日時点でphp.netのリリース一覧に載っている最新は8.4.24(2026年7月30日)で、以下8.4.23(2026年7月2日)、8.4.22(2026年6月4日)と並びます。次のパッチは8月下旬に出る見込みのため、参照時点の最新版はphp.netで確かめてください。稼働環境のphp -vがこの水準から大きく離れている場合は、機能の検討より先にパッチ適用の遅れを解消する話になります。
いま8.4を選ぶ場面と8.5へ飛ばす場面
結論から言えば、2026年後半に着手する新規プロジェクトで8.4を選ぶ理由はありません。8.4のバグ修正は2026年12月31日で止まるため、リリース直後から「セキュリティ修正のみ」の運用に入ることになります。本記事で扱うプロパティフックも非対称可視性も8.5にそのまま残っており、8.4限定の機能を諦める必要もありません。迷う理由がありません。
逆に、8.4を選ぶ判断が成立するのは、依存ライブラリの対応表が8.4止まりのときです。フレームワーク側の対応バージョンは実際に確認する必要があり、たとえばLaravelの要件はLaravel 12の対応PHPバージョンと動作要件で整理しています。8.2や8.3から上げる既存プロジェクトでも、8.4で一度止めるより8.5まで通しで検証したほうが、同じ回帰テストの手間で得られるサポート期間が1年長く残ります。検証は一度で済ませます。
プロパティフック:getter/setterを畳む構文と制約
プロパティフックは、プロパティの読み書きに処理を差し込む構文です。RFCが導入したのはgetとsetの2つのみで、issetやunsetのフックは意図的に見送られています。
getフック・setフックの基本形と仮想プロパティ
フックを持つプロパティのうち、フック内で自分自身($this->プロパティ名)を参照しないものは、値の格納先そのものが作られません。これが仮想プロパティで、ReflectionProperty::isVirtual()で判別できます。
<?php
class Temperature
{
public function __construct(private float $celsius) {}
// 仮想プロパティ: 格納先を持たず、都度計算される
public float $fahrenheit {
get => $this->celsius * 9 / 5 + 32;
set (float $f) { $this->celsius = ($f - 32) * 5 / 9; }
}
// 格納先を持つプロパティ: setフック内の $this->label は生の格納先を指す
public string $label = 'room' {
set (string $v) {
if ($v === '') {
throw new InvalidArgumentException('label must not be empty');
}
$this->label = strtolower($v);
}
}
public function celsius(): float { return $this->celsius; }
}
$t = new Temperature(25.0);
var_dump($t->fahrenheit);
$t->fahrenheit = 212.0;
var_dump($t->celsius());
$t->label = 'Server Room';
var_dump($t->label);
var_dump((new ReflectionProperty(Temperature::class, 'fahrenheit'))->isVirtual());
var_dump((new ReflectionProperty(Temperature::class, 'label'))->isVirtual());
float(77)
float(100)
string(11) "server room"
bool(true)
bool(false)
setフックの中に書いた$this->labelは、フックを経由しない生の格納先への代入になります。ここを普通の代入と同じつもりで書くと無限再帰になる、という誤解が多い箇所ですが、フック内では格納先へ直接書き込まれるため再帰しません。一方の$fahrenheitは格納先を持たないので、$t->fahrenheitの読み取りは毎回計算が走ります。値を何度も読む場所では、計算コストが呼び出し回数ぶん積み上がります。ループの中では変数へ受けておきます。
フックを書けない条件(static・readonly・isset/unset)
プロパティフックには適用できない対象があります。実際にエラーを出させると次のようになります。
<?php
// 以下の3つはそれぞれ別ファイルで実行した結果((1)はパースエラーのため単独で確認)
// (1) unsetフックは構文として存在しない
class A { public string $x { get => 'a'; unset { } } }
// PHP Parse error: syntax error, unexpected token "unset", expecting identifier
// (2) readonlyプロパティにはフックを付けられない
class B { public readonly string $x { get => 'b'; } }
// PHP Fatal error: Hooked properties cannot be readonly
// (3) 静的プロパティは対象外
class C { public static string $x { get => 'c'; } }
// PHP Fatal error: Cannot declare hooks for static property
読み取り専用の値を返したいだけなら、readonlyではなくgetフックだけを持つプロパティにします。フックのないreadonlyと、getフックのみのプロパティは、外から書き込めない点では同じ振る舞いです。
インターフェースでのプロパティ要求
インターフェースがプロパティを要求できるようになったのも8.4の変更点です。{ get; }で読み取り可能、{ set; }で書き込み可能、両方書けば双方を要求します。
<?php
interface HasSlug
{
public string $slug { get; }
}
class Article implements HasSlug
{
public function __construct(private string $title) {}
public string $slug { get => strtolower(str_replace(' ', '-', $this->title)); }
}
$a = new Article('PHP 8.4 Property Hooks');
echo $a->slug, PHP_EOL;
try { $a->slug = 'x'; } catch (Error $e) { echo get_class($e), ': ', $e->getMessage(), PHP_EOL; }
php-8.4-property-hooks
Error: Property Article::$slug is read-only
実装側は$titleという別のプロパティから値を組み立てているだけで、$slugという格納先は存在しません。インターフェースが要求するのは「読める」ことであって、実体のフィールドを持つことではない、という点がメソッドによる契約との違いです。
非対称プロパティ可視性:読み取りと書き込みで別の可視性
非対称プロパティ可視性は、読み取りの可視性と書き込みの可視性を分けて宣言する機能です。public private(set)と書けば、外からは読めるがクラス内からしか書けないプロパティになります。
<?php
class Order
{
public private(set) string $status = 'pending';
public protected(set) int $total = 0;
public function pay(int $amount): void
{
$this->status = 'paid';
$this->total = $amount;
}
}
$o = new Order();
$o->pay(1200);
var_dump($o->status, $o->total);
try { $o->status = 'cancelled'; } catch (Error $e) { echo get_class($e), ': ', $e->getMessage(), PHP_EOL; }
string(4) "paid"
int(1200)
Error: Cannot modify private(set) property Order::$status from global scope
readonlyとの違いは、クラス内であれば何度でも書き換えられる点です。注文ステータスのように状態が段階的に進む値は、readonlyでは表現できずgetterメソッドを足すことになりますが、private(set)ならプロパティのまま外部からの書き込みだけを封じられます。書き込み側の可視性は読み取り側と同じか、より狭い必要があります。読み取りより広い指定はコンパイルエラーです。対象は型付きプロパティに限られ、型のないプロパティに付けるとProperty with asymmetric visibility must have typeで落ちます。
遅延オブジェクト:初期化を触られるまで遅らせる仕組み
遅延オブジェクトは、オブジェクトを生成しておきながら実際の初期化をプロパティやメソッドへ最初に触れた瞬間まで先送りする機能です。DIコンテナのように「登録はするが使われないかもしれない」オブジェクトを扱う場面を想定しています。生成にはリフレクションを使います。
newLazyGhostによる自己初期化
コンストラクタが重いクラスを用意し、初期化がいつ走るかを出力の順序で確かめます。
<?php
class ReportBuilder
{
public array $rows;
public function __construct(public string $sql)
{
echo "[init] heavy setup for {$sql}", PHP_EOL;
$this->rows = ['dummy'];
}
public function count(): int { return count($this->rows); }
}
$r = new ReflectionClass(ReportBuilder::class);
$ghost = $r->newLazyGhost(function (ReportBuilder $o): void {
$o->__construct('SELECT * FROM sales');
});
echo "created", PHP_EOL;
var_dump($r->isUninitializedLazyObject($ghost));
echo "touch ->count()", PHP_EOL;
var_dump($ghost->count());
var_dump($r->isUninitializedLazyObject($ghost));
created
bool(true)
touch ->count()
[init] heavy setup for SELECT * FROM sales
int(1)
bool(false)
[init]の出力がcreatedの直後ではなくtouch ->count()の後に現れている点が要点です。ゴースト方式はオブジェクト自身が後から自分を初期化するため、生成されたインスタンスは最初からReportBuilderそのものであり、instanceofもクラス名の比較も通常のインスタンスと同じ結果になります。
newLazyProxyとの使い分け
同じことをプロキシ方式で書くと、初期化子の戻り値が実インスタンスになります。
<?php
class Conn
{
public function __construct(public string $dsn) { echo "[connect] {$dsn}", PHP_EOL; }
}
$r = new ReflectionClass(Conn::class);
$proxy = $r->newLazyProxy(fn() => new Conn('sqlite::memory:'));
echo "proxy created", PHP_EOL;
echo $proxy->dsn, PHP_EOL;
proxy created
[connect] sqlite::memory:
sqlite::memory:
プロキシ方式は初期化子が実インスタンスを返し、以降のアクセスがそちらへ転送されます。生成物が別インスタンスになるため、オブジェクトの同一性(===)に依存したコードとは相性が悪い一方、コンストラクタを外から呼べないクラスや、生成手順をファクトリにまとめている場合に向きます。既存のコードへ後付けするならゴースト方式、生成をファクトリ関数に閉じ込めているならプロキシ方式です。同一性を見るコードがあるならゴースト一択です。
配列検索関数4種:array_find・array_find_key・array_any・array_all
条件に合う要素を探す処理は、これまでarray_filter()で絞ってからreset()やarray_key_first()を重ねる書き方が定番でした。8.4はこれを4つの関数に置き換えます。コールバックは($value, $key)の順で引数を受け取ります。
4関数の戻り値と見つからなかったときの挙動
4関数がそれぞれ何を返すか、該当なしのときに何が返るかを一度に確認します。
<?php
$users = [
['name' => 'sato', 'role' => 'admin', 'active' => true],
['name' => 'suzuki', 'role' => 'editor', 'active' => false],
['name' => 'takada', 'role' => 'editor', 'active' => true],
];
$isEditor = static fn(array $u): bool => $u['role'] === 'editor';
var_dump(array_find($users, $isEditor)['name']); // 最初に一致した「値」
var_dump(array_find_key($users, $isEditor)); // 最初に一致した「キー」
var_dump(array_any($users, static fn($u) => $u['role'] === 'admin'));
var_dump(array_all($users, static fn($u) => $u['active']));
var_dump(array_find($users, static fn($u) => $u['role'] === 'guest'));
var_dump(array_find_key($users, static fn($u) => $u['role'] === 'guest'));
string(6) "suzuki"
int(1)
bool(true)
bool(false)
NULL
NULL
array_find()は値、array_find_key()はキーを返し、どちらも該当なしでnullを返します。値としてnullやfalseを含みうる配列では、array_find()の戻り値だけで「見つかったか」を判定できません。存在確認が目的ならarray_any()、位置が必要ならarray_find_key()を選びます。戻り値の意味が違う関数です。
空配列でarray_allがtrueを返す落とし穴
件数がゼロのときの戻り値を先に押さえておきます。
<?php
var_dump(array_any([], static fn($v) => true));
var_dump(array_all([], static fn($v) => false));
bool(false)
bool(true)
空配列に対するarray_all()はtrue、array_any()はfalseです。数学的にはどちらも妥当ですが、「全件が検証済みか」をこの関数で判定していると、配列が空のときに検証をすり抜けます。入力が空になりうる箇所では、$items !== [] && array_all(...)のように件数の確認を先に置きます。
DOM刷新:Dom\HTMLDocumentとCSSセレクタ
8.4はDom名前空間にDom\HTMLDocumentとDom\XMLDocumentを追加しました。従来のDOMDocumentはそのまま残るので、既存コードを書き換える必要はありません。新APIの実利はCSSセレクタと文字コードの扱いにあります。
<?php
$html = '<div class="card"><p class="price">1,280円<span>税込</span><ul><li>在庫あり</ul></div>';
$doc = Dom\HTMLDocument::createFromString($html, LIBXML_NOERROR);
echo $doc->querySelector('.card .price')->textContent, PHP_EOL;
echo $doc->querySelector('li')->closest('div')->getAttribute('class'), PHP_EOL;
echo $doc->querySelectorAll('li')->length, PHP_EOL;
echo $doc->saveHtml($doc->querySelector('.card')), PHP_EOL;
1,280円税込
card
1
<div class="card"><p class="price">1,280円<span>税込</span></p><ul><li>在庫あり</li></ul></div>
閉じていない<p>と<li>がHTML5の規則どおり補われ、querySelector()とclosest()がそのまま使えています。DOMDocument側にはquerySelector()が存在せず(method_exists()はfalse)、同じ抽出をDOMXPathで書き直す必要がありました。
文字コードの扱いも変わります。DOMDocument::loadHTML()はメタタグでの文字コード指定がないとUTF-8を前提にしないため、日本語を含むHTMLをそのまま渡すと壊れます。
<?php
$html = '<div class="card"><p class="price">1,280円</p></div>';
$old = new DOMDocument();
@$old->loadHTML($html, LIBXML_NOERROR); // meta charset の指定なし
echo 'old: ', $old->getElementsByTagName('p')->item(0)->textContent, PHP_EOL;
$new = Dom\HTMLDocument::createFromString($html, LIBXML_NOERROR);
echo 'new: ', $new->querySelector('.price')->textContent, PHP_EOL;
old: 1,280å
new: 1,280円
旧APIの出力には、このあとに表示できない制御文字が2つ続きます(掲載時に除去しました)。「円」のUTF-8バイト列をISO-8859-1として解釈した結果です。従来は<meta charset="utf-8">を先頭に足す回避策が必要でしたが、Dom\HTMLDocument::createFromString()はUTF-8として解釈します。HTMLをスクレイピングして値を抜く処理を持っているなら、この2点だけでも新APIへ寄せる価値があります。XPathを書き直す手間はここで回収できます。
PHP 8.3から8.4への変更点と非推奨対応
移行作業で実際に手を動かすのは、新機能の採用より非推奨と破壊的変更の対応です。php-srcのUPGRADINGに載っている項目のうち、アプリケーション側で踏みやすいものを挙げます。
暗黙のnullable型の非推奨と書き換え
デフォルト値にnullを書いた引数が、型宣言なしで自動的にnull許容になる書き方が非推奨になりました。既存コードで最も多く警告が出る箇所です。
<?php
// 8.4 で Deprecated
function findUser(string $name = null): string { return $name ?? 'guest'; }
// Deprecated: findUser(): Implicitly marking parameter $name as nullable is deprecated,
// the explicit nullable type must be used instead
// 書き換え後(警告なし)
function findUserFixed(?string $name = null): string { return $name ?? 'guest'; }
直し方は型の前に?を足すだけで、挙動は変わりません。ここは機械置換で片づきます。件数が多くても一括で直せるため、8.4へ上げる前にこの1点だけ先に潰しておくとログが読みやすくなります。
移行前に確認する破壊的変更
| 項目 | 8.3まで | 8.4での変更 |
|---|---|---|
| opcache.jit | tracing(既定) | disable(既定) |
| opcache.jit_buffer_size | 0(既定) | 64M(既定) |
| E_STRICT定数 | 有効 | 非推奨・エラーレベルは削除 |
| exit / die | 言語構造 | 関数相当・型エラーを投げうる |
| PHP_DEBUG / PHP_ZTS | int | bool |
| password_hash のbcryptコスト | 10(既定) | 12(既定) |
| imap / oci8 / PDO_OCI / pspell | 本体同梱 | PECLへ分離(同梱終了) |
| dba_open / dba_popen / odbc_connect / odbc_pconnect / odbc_result | resource | オブジェクト(is_resourceが不成立) |
ただし、どちらのバージョンでもJITは既定で無効のままです。影響を受けるのはopcache.jit_buffer_sizeだけで有効化していた環境に限られます。8.3以前はopcache.jit_buffer_sizeにサイズを指定するだけでJITが有効になりましたが、8.4はopcache.jitがdisableに変わったため、この書き方の設定ファイルを持ち込むとJITが無効のまま動きます。有効化するにはopcache.jitにモードを明示します。OPcache自体の設定はOPcacheとは?読み方とPHP 8.5の変更点・設定・確認方法で扱っています。is_resource()による戻り値チェックも、DBAやODBCを使っていれば必ず落ちるので、falseやnullとの比較へ置き換えます。bcryptのコスト既定値が10から12へ上がった点も見落としがちです。ハッシュ計算の所要時間が約4倍になるため、ログイン処理のレスポンスを実測しておきます。imap・oci8・PDO_OCI・pspellはPECLへ分離されたので、これらを使っている環境は移行前にPECL版の導入可否を確認します。
静的解析での洗い出し
非推奨の検出は実行時の警告任せにせず、静的解析で先に洗い出すほうが確実です。テストが通らないパスの引数まで警告が出ないためです。PHPStanやPsalmは対象PHPバージョンを指定して解析でき、暗黙のnullableのような構文レベルの問題はコードを動かさずに列挙できます。ツールの選び方はPsalmとPHPStanの違いを比較にまとめています。
小粒でも効く追加:#[\Deprecated]属性・new式の括弧省略・数値と文字列の新API
ここまでの機能に隠れがちですが、日々のコードで触る頻度が高いのは次の5つです。順に見ていきます。
#[\Deprecated]属性による段階的な廃止
<?php
#[\Deprecated(message: "use formatPrice() instead", since: "2.3.0")]
function format_price(int $yen): string { return number_format($yen) . '円'; }
echo format_price(1280), PHP_EOL;
Deprecated: Function format_price() is deprecated since 2.3.0, use formatPrice() instead in /private/tmp/demo/legacy.php on line 5
1,280円
#[\Deprecated]属性を付けた関数やメソッドを呼ぶと、PHP自身の非推奨と同じ形式でE_USER_DEPRECATEDが出ます。docblockの@deprecatedと違い実行時に検出できるため、社内ライブラリの段階的な廃止に使えます。messageとsinceはどちらも省略できます。
new式の外側括弧の省略とBcMath\Number
省略できるのは式全体を囲む括弧で、コンストラクタ引数の括弧は従来どおり必要です。
<?php
class Cfg
{
public function __construct(private array $v) {}
public function get(string $k): ?string { return $this->v[$k] ?? null; }
}
// 8.3までは (new Cfg([...]))->get('env') と括弧が必要だった
echo new Cfg(['env' => 'prod'])->get('env'), PHP_EOL;
$a = new BcMath\Number('0.1');
$b = new BcMath\Number('0.2');
var_dump((string)($a + $b), ($a + $b) == new BcMath\Number('0.3'));
var_dump(bcround('2.345', 2), bcceil('2.1'), bcfloor('-2.1'), bcdivmod('17', '5'));
prod
string(3) "0.3"
bool(true)
string(4) "2.35"
string(1) "3"
string(2) "-3"
array(2) {
[0]=>
string(1) "3"
[1]=>
string(1) "2"
}
コンストラクタ引数付きのnew式がそのままメソッド呼び出しにつながるようになり、外側の括弧が不要になりました。BcMath\Numberは演算子オーバーロードを備えた値オブジェクトで、0.1 + 0.2が0.3と等しくなる点が浮動小数点数との違いです。金額計算でbcadd()の文字列引数を積み重ねていた処理は、この形に置き換えると読みやすくなります。bcround()・bcceil()・bcfloor()・bcdivmod()も8.4での追加です。
round()の丸めモード指定
8.3までのround()は四捨五入(ゼロから遠いほうへ寄せる方式)で固定でした。8.4はRoundingMode enumで丸め方を選べます。
<?php
// 8.3までの round() は四捨五入で固定だった
var_dump(round(2.5), round(3.5));
// 8.4 は RoundingMode enum で丸め方を選べる
var_dump(round(2.5, 0, RoundingMode::HalfEven));
var_dump(round(3.5, 0, RoundingMode::HalfEven));
var_dump(round(-2.5, 0, RoundingMode::TowardsZero));
var_dump(round(2.1, 0, RoundingMode::PositiveInfinity));
float(3)
float(4)
float(2)
float(4)
float(-2)
float(3)
用意されている定数はHalfAwayFromZero・HalfTowardsZero・HalfEven・HalfOdd・TowardsZero・AwayFromZero・NegativeInfinity・PositiveInfinityの8つです。会計処理で使う銀行家丸め(HalfEven)を自前で実装していたなら、その関数は不要になります。
マルチバイト対応のtrim・ucfirst
日本語を扱う実装で効くのがこの追加です。trim()は全角スペースを落としませんが、mb_trim()は落とします。
<?php
var_dump(mb_trim(" 全角スペース混じり "));
var_dump(mb_ucfirst('php 8.4'));
var_dump(function_exists('mb_ltrim'), function_exists('mb_rtrim'), function_exists('mb_lcfirst'));
string(27) "全角スペース混じり"
string(7) "Php 8.4"
bool(true)
bool(true)
bool(true)
フォーム入力の前後にある全角スペースをpreg_replace()で削っていた処理は、mb_trim()に置き換えられます。mb_ltrim()・mb_rtrim()・mb_lcfirst()も同時に入りました。
PDOのドライバ別サブクラス
接続にPdo\Sqlite::connect()のような静的メソッドを使うと、ドライバ固有のサブクラスが返ります。
<?php
$pdo = Pdo\Sqlite::connect('sqlite::memory:');
var_dump(get_class($pdo));
var_dump(get_class(new PDO('sqlite::memory:')));
string(10) "Pdo\Sqlite"
string(3) "PDO"
従来のnew PDO()はPDOのままなので、既存コードの挙動は変わりません。Pdo\Sqlite・Pdo\Mysql・Pdo\Pgsqlなどにはドライバ固有のメソッドが載るため、ドライバ判定のifを減らせます。
よくある質問
PHP 8.4はいつまでサポートされますか?
アクティブサポート(バグ修正とセキュリティ修正)は2026年12月31日まで、セキュリティ修正のみの期間は2028年12月31日までです。2026年8月時点では、バグ修正が受けられる期間が4か月ほど残っている状態にあたります。
PHP 8.4の最新バージョンはどれですか?
2026年8月20日時点では8.4.24(2026年7月30日)が最新です。8.4系は4週ごとにパッチが出ているため、参照する時点の最新版はphp.netのダウンロードページで確認してください。なお8.4系ではなくPHP本体の最新版を探している場合は、PHP 8.5の変更点と新機能まとめが対象になります。
PHP 8.3から8.4へ上げるとき、最初に直すのはどこですか?
暗黙のnullable型(string $x = null)の非推奨です。件数が多い代わりに?string $x = nullへの機械的な置換で済みます。次に確認するのはopcache.jitの既定値変更と、is_resource()でDBA・ODBCの戻り値を判定している箇所です。
プロパティフックはgetterとsetterを完全に置き換えられますか?
置き換えられない場面があります。静的プロパティとreadonlyプロパティにはフックを書けず、issetやunsetに対応するフックも存在しません。値の存在確認や破棄に処理を挟みたい場合は、従来どおり__isset()や__unset()を使います。
array_findとarray_search、array_filterはどう使い分けますか?
array_search()は値そのものの一致で探す関数、array_find()はコールバックの条件で探す関数です。array_filter()は該当する全要素を配列で返すため、1件だけ欲しい場面ではarray_find()のほうが余分な配列を作らずに済みます。条件に合うものがあるかだけを知りたいならarray_any()です。