PHP 8.5: обиколка на идващите възможности

Новите възможности в PHP 8.5: pipe operator, #[NoDiscard], static closure constants, array_first/last, intl и debug подобрения. Какво да приемеш първо.

TL;DR стълбица на хайпа (моето игриво класиране)

  1. Pipe Operator (|>) – четими, линейни трансформации. Блаженство. Магнит за refactor-и.
  2. #[\NoDiscard] attribute – превръща „забравихме да използваме върнатата стойност“ в моментално предупреждение. Прекрасно си пасва с pipe-овете.
  3. Static Closures / First-Class Callables in Constant Expressions – compile-time strategy maps и attribute arguments. Бонбон за framework-и.
  4. php --ini=diff – мигновен diff на environment drift. Спестява ровене из конфигурации.
  5. Attributes on Global & Class Constants – metadata навсякъде: flags, deprecations, semantic tags.
  6. array_first() / array_last() – очевидни, показват намерение, не мутират. Сбогом, странични ефекти от reset().
  7. get_exception_handler() и компания – introspection за layered error handling. Победа на framework / infra ниво.
  8. Intl благинки (IntlListFormatter, Locale::isRightToLeft()) – по-гладък, локализиран UX с почти никакъв код.
  9. Grapheme-aware Levenshtein – user-facing fuzzy matching, който наистина уважава човешките символи.
  10. Directory Object + cURL / Build Introspection / Misc – полиране за консистентност и operability.

(Да, твоят ред може да е друг. Това му е забавното — спорете на кафе.)


1. Pipe Operator (|>) – “Easy Peasy Lemon Squeezy”

Вложени извиквания и временни променливи за изхвърляне? Няма ги. Pipe operator-ът взема стойността отляво и я подава като първи аргумент на callable-а отдясно. Четеш отгоре надолу, логиката тече като проза, а намерението те плясва през лицето.

Преди (подскачане през променливи):

$email = $request->string('email');
$email = trim($email);
$email = strtolower($email);
sendEmail($email);

Преди (гнездо от скоби):

sendEmail(strtolower(trim($request->string('email'))));

След това (pipe дзен):

$request->string('email')
    |> trim(...)
    |> strtolower(...)
    |> sendEmail(...);

Защо има значение:

  • Визуален поток на данните. Няма умствен стек от вложени return-и.
  • Комбинира се прекрасно с малки pure helper-и.
  • Насърчава разбиването на трансформациите в именувани функции / closures.
  • Автоматично удовлетворява #[\NoDiscard], защото стойността продължава да се движи.

Style Tip: Дръж всяка стъпка без side effects; запази последния pipe за ефект (например persist, send, emit), за да виждаш с един поглед къде свършва „чистотата“.


2. #[\NoDiscard] – намерение, превърнато в оръжие

Колко фини бъгове са били просто „извикахме нещото, но забравихме да използваме какво връща“? Маркираш функция или метод с #[\NoDiscard], за да изискаш резултатът му да бъде използван — или съзнателно игнориран чрез (void) cast.

#[\NoDiscard("Token must be used – did you forget to persist or dispatch?")]
function issueAuthToken(User $user): string {
    return generateTokenFor($user);
}

issueAuthToken($user); // ⚠ Emits warning in 8.5
(void) issueAuthToken($user); // Explicit intentional discard

Patterns:

  • Result objects (Result, Outcome, ValidationReport).
  • Immutable builders (връщат нов instance при всяко извикване).
  • Security / side-effect gating (tokens, signatures).

Synergy: В pipeline return-ът на всяка стъпка по дефиниция се консумира от следващата, така че случайните discard-и изчезват.


3. Static Closures in Constant Expressions – “Wait… what?!”

Вече можеш да вграждаш static closures (или first-class callables) в constant expressions, default property values, attribute arguments и default parameter arrays. Мисли за compile-time registries без boot-time wiring акробатика.

class Sanitizers {
    public const STAGES = [
        'trim' => trim(...),
        'upper' => static function (string $v): string { return strtoupper($v); },
    ];
}

// Attribute example
#[Validate(
    rules: [
        'title' => static function(string $v){ return mb_strlen($v) > 0; },
        'slug'  => static function(string $v){ return preg_match('/^[a-z0-9-]+$/', $v); },
    ]
)]
class Article {}

Защо удря добре:

  • Премахва service-locator lookup-и за прости strategies.
  • Бута pure mapping tables в constants (immutable + cacheable).
  • Attributes вече могат директно да капсулират логика — не само scalar metadata.

Ограничение: Трябва да е static; няма $this, няма variable capture. Ако ти трябва context, подай го експлицитно по-късно.


4. php --ini=diff – рентген за config drift

Омръзнало ли ти е от „ама на staging работи“? Този CLI flag отпечатва само INI директивите, които се различават от default-а.

php --ini=diff
# memory_limit: "128M" -> "-1"
# max_execution_time: "30" -> "0"

Use Cases:

  • CI step за налагане на консистентен baseline.
  • Бърз sanity check, когато worker се държи странно.
  • Triage на memory/time аномалии.

Pro tip: Запиши output-а във version control като runtime baseline.


5. Attributes on Global & Class Constants – metadata навсякъде

Constants порастват от „тъпа стойност“ до „анотиран участник“. Украси domain flags, feature toggles, deprecation notices, unit semantics — директно на мястото на дефиницията.

#[Deprecated("Use FEATURE_NEW_PRICING instead")]
public const FEATURE_OLD_PRICING = 1;

#[Unit("ms")]
public const DEFAULT_TIMEOUT = 250;

Framework leverage: Автоматично откриване на deprecations, захранване на feature catalogs, генериране на docs или налагане на policy чрез reflection.


6. array_first() / array_last() – очевидното най-сетне съществува

Спри с pointer акробатиката (reset(), end()) или slicing-а само за да надникнеш. Тези helper-и четат намерението директно и не мутират вътрешното състояние на array-а.

$firstUser = array_first($users, default: null);
$lastUser  = array_last($users, default: null);

Refactor pattern: Grep-ни за reset( / end( / сложни array_slice(..., 0, 1) — замени ги със semantic calls. По-чисти diff-ове, по-малко micro-bug-ове.


7. get_exception_handler() (и по-добри fatal traces) – upgrade за observability

Framework / infra разработчици, радвайте се: вече можеш да introspect-неш активния exception handler. Chain, wrap, restore или decorate без крехко глобално жонглиране.

$previous = get_exception_handler();
set_exception_handler(function(Throwable $e) use ($previous) {
    logToSentry($e);
    if ($previous) { $previous($e); }
});

В комбинация с по-богати stack traces при fatal errors, production post-mortem-ите се ускоряват драматично.


8. Intl подобрения – човешки списъци и посока

IntlListFormatter рендерира чаровни, locale-aware conjunctions/disjunctions без ръчно лепене на текст.

$f = new IntlListFormatter('pt_PT', 'conjunction');
echo $f->format(['Lisboa', 'Porto', 'Coimbra']); // "Lisboa, Porto e Coimbra"

$fOr = new IntlListFormatter('en_US', 'disjunction');
echo $fOr->format(['apples', 'bananas', 'cherries']); // "apples, bananas, or cherries"

Комбинирай с Locale::isRightToLeft() (или locale_is_right_to_left()), за да превключваш layout direction автоматично.


9. Grapheme-Aware Levenshtein – истинска дистанция между user strings

Когато потребителите пишат emoji, accents, combining characters — byte distance или наивна codepoint distance лъже. grapheme_levenshtein() уважава видимите символи.

grapheme_levenshtein('café', 'cafe'); // 0 – visually same after accent

Search suggestions, fuzzy match и typo-tolerant login flows стават лингвистично честни.


10. Парадът на полирането

Directory Object: opendir() вече ти дава proper object (type safety, бъдещо разширяване) вместо legacy resource.

cURL Enhancements: По-добри share handles + multi-handle introspection = подобрено connection reuse в long-lived workers (мисли RoadRunner, Swoole) и по-фин performance tuning.

PHP_BUILD_DATE: Бърза проверка „колко стар е този binary?“ за audit scripts. Чудесно за гаранция, че fleet nodes не изостават тихомълком.


Cheat sheet за feature synergy

Цел Комбинирай
Pipeline от трансформации с enforced usage `
Declarative validation / strategy maps Constant-expression static closures + constant attributes
По-безопасни refactor-и на legacy arrays array_first()/array_last() + strict return typing
Production incident triage По-добри fatal stack traces + php --ini=diff + get_exception_handler()
International UX polish IntlListFormatter + direction detection + grapheme distance

Практичен план за приемане

  1. Въведи Pipe Operator постепенно: започни в pure data normalization слоеве; налагай style (един side effect на опашката) в code review.
  2. Анотирай критични APIs с #[\NoDiscard]: фокусирай се първо върху security, persistence и builders — измервай броя warnings в CI.
  3. Refactor-ни Strategy Tables: премести простите callable maps в public const arrays със static closures за zero-boot cost.
  4. Config Drift Checks: добави CI job, който capture-ва output-а на php --ini=diff; alert-вай при неочаквани промени.
  5. Metadata Sweep: маркирай constants с deprecation / units / feature flags, за да захраниш internal tooling.
  6. Array Edge Extraction Cleanup: codemod за замяна на pointer-manipulating patterns.
  7. Error Handler Layering: обвий съществуващите global handlers чрез get_exception_handler() за observability (Sentry/new relic instrumentation).
  8. i18n Enhancements: смени ръчния “list glue” код с IntlListFormatter; тествай RTL layout autoselection.
  9. Fuzzy Matching Quality: където се появява user-generated multilingual text (search, tagging), benchmark-ни grapheme спрямо classic distance.
  10. Runtime Audit Script: логвай PHP_BUILD_DATE + php --ini=diff всеки ден, за да хващаш стареещи containers.

Pitfalls & Gotchas

Item За какво да внимаваш Mitigation
Pipe operator misuse Side effects по средата на pipeline Ограничаване до pure funcs до final stage
#[\NoDiscard] overuse Noise fatigue (warning blindness) Прилагай само върху семантично критични return-и
Static closure limits Нужен е captured context Подай context като explicit param или factory returning closure
Constant attributes sprawl Metadata fragmentation Установи internal attribute naming conventions
i18n list formatting Предположения за punctuation styling Snapshot tests per locale

Мини playground „Show Me“

#[NoDiscard("Hash must be stored or compared")]
function password_hash_safe(string $plain): string {
    return password_hash($plain, PASSWORD_DEFAULT);
}

function sanitize_email(string $raw): string { return strtolower(trim($raw)); }

$request->string('email')
    |> sanitize_email(...)
    |> fn($email) => (strlen($email) > 5 ? $email : throw new InvalidArgumentException('Too short'))
    |> sendEmail(...); // Each stage consumes prior result – no discard.
class Rules {
    public const VALIDATORS = [
        'title' => static function(string $v){ return $v !== ''; },
        'slug'  => static function(string $v){ return (bool) preg_match('/^[a-z0-9-]+$/', $v); },
    ];
}

foreach (Rules::VALIDATORS as $field => $check) {
    if (! $check($data[$field] ?? '')) {
        throw new RuntimeException("Invalid $field");
    }
}

Кога да не посягаш към лъскавото

  • Една тривиална трансформация? Pipe може да е overkill; strtolower($x) още е напълно добре.
  • Context-heavy closures? Обикновени methods с dependency injection > static closure hacks.
  • Legacy codebase по средата на upgrade? Въвеждай по една възможност наведнъж, за да избегнеш cognitive churn.

Recap на mental model-а

Feature Основен mental model
` >`
#[\NoDiscard] Налага намерена консумация (използвай или игнорирай с (void))
Static closure constants Immutable strategy registry, подготвен при load time
Attributes on constants First-class metadata channel за tooling и policies
array_first()/last() Declarative, non-mutating edge access
php --ini=diff Config delta lens спрямо default baseline
get_exception_handler() Introspect и wrap на global exception flow
Intl additions Вградена locale intelligence вместо handcrafted glue
Grapheme distance Операции върху човешки възприемани символи, не raw codepoints
Build & resource polish Incremental standardization и introspection

Финални vibes

PHP 8.5 не крещи с paradigm shifts — шепне безмилостни ергономични победи. Комбото pipe operator + #[\NoDiscard] само по себе си ще побутне кода ти към по-ясно намерение. Добави compile-time closures и constant attributes, и framework-ите/компонентите ти започват да се усещат по-declarative, по-explicit, по-discoverable. Bam bam boom — ship it.

Твой ход: Избери една възможност (вероятно pipe-а), приложи я хирургически в малък module, измери яснотата през feedback-а в code review, после разшири. Momentum beats big-bang rewrites.

Остани игрив, refactor-вай смело и — да — пиши на твоите Taylors, когато намериш моментите „Wait, WHAT?!“.

Happy coding.


Коментари

Boris D. Teoharov

Автор

Здравей, аз съм Борис

Не съм писател. Не съм философ. Просто съм backend инженер от България, който живее между Laravel опашки и индекси със стотици милиони редове. През останалото време чета медицина, която няма работа да чета, френски романи, които разбирам наполовина, и каквото още малката ми гумена глава реши да дъвче. Две спасени кучета ме държат честен.