PHP
PHP 8 is a gradually typed language with enums, readonly properties, attributes and fibers, run per request under a share-nothing model. Interviewers test whether your PHP is post-8.0.
on this pageshowhide
guide
overview
~2 minPHP interviews open by finding out which PHP you write. Someone who learned the language before 8.0 answers questions on equality, types and failure in ways that are now wrong or only half right, so expect the first minutes on loose versus strict comparison, how a declared scalar parameter treats a value of the wrong type, and why an `Error` is not an `Exception`. The object model comes next — constructor promotion, readonly and hooked properties, traits, late static binding, enums — because that is the part the 8.x releases reshaped most. Almost every PHP program answers HTTP requests, so interviewers then move to the request itself and to the habits that keep it safe: escaping output, binding query parameters, hashing passwords. Senior rounds ask where the code actually runs: what the per-request lifecycle means for state, what OPcache and the JIT change, and what breaks when an application moves into a long-running worker. The hub follows those seams. The language core is [Statements & Expressions](/topics/lang-php-syntax), [Scalar & Compound Types](/topics/lang-php-types-syntax), [Array Operations](/topics/lang-php-arrays-strings), [Text Processing](/topics/lang-php-text), [Functions & Callables](/topics/lang-php-functions) and [Object-Oriented PHP](/topics/lang-php-oop), with [Errors & Exceptions](/topics/lang-php-error-handling), [Code Organization](/topics/lang-php-namespaces) and [Code Metadata & Introspection](/topics/lang-php-metaprogramming) beside it. The web layer is [Serving HTTP Requests](/topics/lang-php-request-handling), [Secure Coding](/topics/lang-php-security) and [Database Access](/topics/lang-php-database). Reaching outside the process is [Files & Streams](/topics/lang-php-files), [Data Formats](/topics/lang-php-formats), [Outbound HTTP](/topics/lang-php-http-clients) and [Host OS Interaction](/topics/lang-php-system). The runtime layer is [Engine & Runtime Setup](/topics/lang-php-runtime), [Concurrency Options](/topics/lang-php-concurrency), [Speed & Footprint](/topics/lang-php-performance) and the [Standard Library Extras](/topics/lang-php-stdlib), and [PHP-FIG Standards](/topics/lang-php-standards) covers the shared interfaces that let libraries from different authors fit together. Learn it roughly in that order. Syntax and types come first, since comparison and coercion rules decide many questions that look as if they are about something else. Arrays next: they carry nearly all PHP data, and their key handling surprises anyone used to separate list and map types. Then functions and objects, the bulk of framework code. Read the runtime section early rather than last — the lifecycle explains sessions, caching and every worker-mode question. Security, databases and the standards weigh most in backend roles, which is where most PHP work is.
primer
### Each request is a whole program run Under PHP-FPM or an Apache module, every HTTP request starts from empty application state: the script is loaded, runs, answers, and everything it built is discarded. Much of the language's behaviour follows from that. Anything that must outlive a request belongs in a session store, a database or a cache; a request that leaks memory or leaves state half-built rarely affects the next one; and OPcache matters because without it the compile step would repeat on every hit. Worker runtimes give up this model on purpose to save the bootstrap cost, and senior questions test whether you know what they give up with it. ### Two regimes in one type system PHP is dynamically typed, with declarations layered on top. In the default coercive mode a declared scalar parameter converts a compatible value; `declare(strict_types=1)` in the calling file turns that conversion off. Comparison has the same split: `==` converts before it compares, `===` does not. PHP 8.0 made string-to-number comparison far less surprising, but it did not remove loose comparison, and several constructs still use it by default. Knowing which rule a given construct applies is half of many answers. ### One array type does every job List, dictionary, set, stack and record are all the same ordered map, keyed by integers or strings. Some helpers keep keys and some renumber them, and that choice sits behind most array bugs. Arrays and strings are values that copy lazily when written; objects are handles, so two variables can refer to one object and `clone` is needed for a separate one. ### Strings are bytes A PHP string carries no encoding. The classic string functions work on bytes, which is correct for ASCII and wrong for most of the world's text; the multibyte extension and the Unicode mode of PCRE work on characters. ### Failure has two histories Older PHP reported problems as warnings and notices, or by returning `false`; PHP 7 introduced the `Throwable` tree and PHP 8 moved many more engine and internal-function failures onto it. Both models are still live: some functions throw, some return a sentinel, a few set a last-error value. Answers are judged on whether you check failure the way that particular API reports it. ### Modern PHP says in syntax what it once said in convention Enums, readonly properties, constructor promotion, attributes and property hooks replaced class constants, docblock annotations, magic methods and hand-written boilerplate with constructs the engine checks. First-class callables did the same for callbacks written as strings and arrays. Interviewers use them to date your knowledge, and a design answer built on `__get` or on `@annotations` in comments reads as pre-8. ### The ecosystem is held together by interfaces Composer installs packages and generates the autoloader; namespaces map onto directories by the PSR-4 convention; PHP-FIG publishes the interfaces for HTTP messages, logging, caching and containers that frameworks agree on. Much of professional PHP is choosing the interface to depend on rather than the library behind it.
- Type juggling
- PHP's automatic conversion of a value to the type an operator or context expects, such as a numeric string used in arithmetic or any value tested in an if.
- Strict types
- A per-file declare directive that makes scalar type declarations reject values of the wrong type instead of converting them; for arguments, the calling file's mode decides.
- DNF type
- Disjunctive normal form: a type declaration that combines unions of intersection types, such as an intersection in parentheses joined to null with a vertical bar.
- Copy-on-write
- The engine's lazy copying of arrays and strings: an assignment shares the underlying value, and a real copy is made only when one side is modified.
- Superglobal
- One of PHP's built-in arrays, such as $_GET, $_POST, $_SERVER or $_SESSION, visible in every scope without a global statement.
- SAPI
- Server API: the layer that connects the engine to its host, such as PHP-FPM, the Apache module, the CLI or the built-in development server.
- OPcache
- The bundled extension that keeps compiled opcodes in shared memory so later requests skip parsing and compiling; it is also where the JIT lives.
- Constructor promotion
- PHP 8.0 syntax that declares and assigns a property directly from a constructor parameter marked with a visibility, removing the repeated field-and-assignment boilerplate.
- Readonly property
- A typed property that can be initialised once and never changed afterwards; a readonly class applies it to every property it declares.
- Property hook
- PHP 8.4 get and set logic attached to a property declaration itself, replacing hand-written accessor methods and many uses of magic methods.
- Backed enum
- An enumeration whose cases each carry a unique int or string value, with from and tryFrom to map stored values back to cases.
- Trait
- A reusable block of methods and properties copied into a class at compile time; PHP's answer to sharing code across single-inheritance hierarchies.
- Late static binding
- Resolving static:: to the class a call was made on at run time, rather than to the class where the method was written.
- Attribute
- Structured metadata written with #[...] on a class, method, property, parameter or constant, readable through Reflection; frameworks use it for routing, validation and wiring.
- Autoloader
- A callback registered with the engine that loads a class's file the first time an undefined class name is used; Composer generates one from namespace-to-directory rules.
- Throwable
- The interface at the root of everything PHP can throw, with two branches: Error for engine-detected faults and Exception for failures code reports.
- Fiber
- A PHP 8.1 primitive for pausing a call stack and resuming it later, on the same thread; the building block event-loop libraries use for asynchronous code.
- PSR
- PHP Standard Recommendation: an interface or convention published by PHP-FIG, such as autoloading, HTTP messages or logging, that independent libraries implement.
The sections stack. Syntax, types and arrays define what a value is and how two values compare; almost every later answer leans on them. Functions and objects build on that core — closures decide what they capture, classes decide what is mutable and what is inherited — and [Code Organization](/topics/lang-php-namespaces) with [Code Metadata & Introspection](/topics/lang-php-metaprogramming) explains how a framework finds, loads and wires those classes without being told about each one. Errors cut across all of it, because each layer reports failure in its own dialect. The web layer then applies the core to one HTTP exchange. [Serving HTTP Requests](/topics/lang-php-request-handling) is where untrusted data enters, [Secure Coding](/topics/lang-php-security) is where it is checked on the way in and escaped on the way out, and [Database Access](/topics/lang-php-database) is where it must never become query text. [Data Formats](/topics/lang-php-formats), [Files & Streams](/topics/lang-php-files), [Outbound HTTP](/topics/lang-php-http-clients) and [Host OS Interaction](/topics/lang-php-system) are the same concern at other boundaries: input you did not write, failures you did not cause, and memory that grows with the size of what you read. Underneath sits the runtime. [Engine & Runtime Setup](/topics/lang-php-runtime) explains the lifecycle every other section silently assumes; [Speed & Footprint](/topics/lang-php-performance) explains what that lifecycle costs; [Concurrency Options](/topics/lang-php-concurrency) is what you reach for when one request at a time on one thread is not enough. The standards section closes the loop: its interfaces are how frameworks agree on requests, responses, logs and containers, so they sit on top of everything else. A small example where several core ideas meet: ```php <?php declare(strict_types=1); enum Status: string { case Draft = 'draft'; case Paid = 'paid'; } final class Invoice { public function __construct( public readonly int $cents, public readonly Status $status = Status::Draft, ) {} } function label(Invoice $i): string { return match ($i->status) { Status::Draft => 'draft', Status::Paid => sprintf('paid %.2f', $i->cents / 100), }; } ``` Promotion declares both properties in the constructor signature; `readonly` means an invoice cannot be edited after it is built, only replaced; the backed enum maps cleanly to a database column; and `match` compares strictly and fails loudly on a case it does not handle, where a `switch` compares loosely and quietly does nothing. With strict types on, passing `'1999'` for the amount from this file throws instead of converting.
- Statements & Expressions →
Tags, scope, operators and control flow; the loose-versus-strict comparison rules introduced here reappear in nearly every later section.
- Scalar & Compound Types →
Coercive and strict modes, union and nullable declarations, and float precision: the rules that decide what a function actually receives.
- Array Operations →
The ordered map behind every array, the helpers that keep or renumber keys, and sorting; most PHP data passes through it.
- Functions & Callables →
Parameters, named arguments, closure capture and generators: how values move into and out of the code you write.
- Object-Oriented PHP →
The largest section and the one that dates your PHP: promotion, readonly, traits, late static binding, enums and property hooks.
- Engine & Runtime Setup →
With the language in hand, learn where it runs: the per-request lifecycle, SAPIs and php.ini explain state, caching and deployment answers.
Answering with pre-8.0 behaviour: string-to-number comparison, internal-function errors and several warnings changed in 8.0, and interviewers notice an answer that is out of date.
Testing the result of a search or validation function with a plain
if: a valid0or empty string is falsy, so compare againstfalsewith!==or===.Assuming
declare(strict_types=1)applies globally: argument checks follow the mode of the calling file, so a strict library still coerces when called from non-strict code.Expecting array helpers to preserve keys, or to renumber them, without checking: the behaviour differs between functions and between integer and string keys.
Measuring or slicing user text with byte functions: anything beyond ASCII needs the multibyte functions, or lengths and cuts land mid-character.
Catching
Exceptionand believing every failure is covered: engine faults such as type errors sit on theErrorbranch and pass straight through.Escaping input on the way in instead of output on the way out: escaping belongs to the context the value is written into, HTML, SQL or shell.
Keeping request data in a static property or singleton and then moving to a long-running worker: the next request on that process inherits it.
Calling
unserialize()on data a client can control: it can instantiate arbitrary classes and trigger their magic methods, so untrusted input should travel as JSON.Reaching for Fibers to make CPU-bound work faster: they interleave on one thread, so they help only when an event loop overlaps time spent waiting on I/O.
This guide assumes PHP 8.4 or 8.5; answers that depend on a newer release say so. PHP's history matters in interviews because code written for 5.x and 7.x is still in production and the idioms differ sharply: - **7.0** — scalar parameter and return declarations with the `strict_types` directive, and the `Throwable` hierarchy with `Error` alongside `Exception`. - **7.4** — typed properties and arrow functions. - **8.0** — constructor promotion, `match`, the nullsafe operator, union types, attributes, named arguments and the JIT; saner string-to-number comparison. - **8.1** — enums, readonly properties, Fibers, first-class callable syntax. - **8.2** — readonly classes and DNF types; dynamic properties deprecated. - **8.3** — type declarations on class constants, and the `#[\Override]` attribute. - **8.4** — property hooks and asymmetric visibility. - **8.5** — the pipe operator and clone-with. The shift interviewers probe most is what a class looks like before and after 8.0: ```php // 7.x class Money { private $cents; public function __construct($cents) { $this->cents = $cents; } public function getCents() { return $this->cents; } } // 8.1 onward final class Money { public function __construct(public readonly int $cents) {} } ``` The same idea in a fraction of the code, and the engine now enforces the type and the immutability that the older version only promised in its method names.
PHP is maintained by its open-source community, and the language is steered through a public RFC vote. In practice a candidate is expected to place it in four settings. **Frameworks.** Most application work happens inside Laravel or Symfony, both built on Composer packages and PHP-FIG interfaces; WordPress and other content systems account for a large share of PHP running on the web, with their own older conventions. **Package management.** Composer is the standard: it resolves dependencies and generates the autoloader, so a question about loading classes usually expects a Composer answer. **Serving.** The classic deployment is nginx in front of PHP-FPM; FrankenPHP, RoadRunner and Swoole instead keep the application booted in long-running workers, trading the per-request clean slate for speed. **Quality tooling.** PHPUnit for tests, and static analysers such as PHPStan and Psalm that check types the engine only enforces at run time. The comparison that comes up most is with Node.js, Python and Ruby for web backends. A fair answer names what separates PHP: request isolation by default instead of one long-lived process, a standard library aimed squarely at the web, and deep roots in hosting and content platforms. The trade-off is that concurrency and long-running work need a different runtime model from the one PHP starts with.
explore
- Engine & Runtime Setup20 questions
- Share-Nothing Lifecycle5 questions
- SAPIs & Built-In Server4 questions
- INI Directives & Extensions5 questions
- Versions & Deprecations6 questions
- Statements & Expressions28 questions
- Tags & Echo Output6 questions
- Variable Scope & References6 questions
- Constants & Magic Constants5 questions
- Operators & Equality6 questions
- Branching & Loops5 questions
- Scalar & Compound Types21 questions
- Juggling & Casting6 questions
- Declarations & Strict Mode5 questions
- Union, Nullable & DNF5 questions
- Numbers & Precision5 questions
- Functions & Callables20 questions
- Parameters & Named Arguments5 questions
- Closures & Short Closures4 questions
- Invocation Forms & Pipes5 questions
- Yield & Lazy Sequences6 questions
- Array Operations21 questions
- Ordered Hash Map Model5 questions
- Transform & Search Helpers6 questions
- Sorting & Comparators5 questions
- Destructuring & Spread5 questions
- Text Processing23 questions
- Quoting & Interpolation5 questions
- String Functions & Formatting6 questions
- Multibyte & UTF-86 questions
- PCRE Regular Expressions6 questions
- Object-Oriented PHP47 questions
- Instances & Cloning6 questions
- Constructors & Promotion5 questions
- Typed & Readonly Properties5 questions
- Inheritance & Overriding5 questions
- Interfaces & Contracts5 questions
- Traits5 questions
- Static Members & Late Binding5 questions
- Magic Methods6 questions
- Enumerations5 questions
- Code Organization15 questions
- Namespaces & Imports5 questions
- Include & Require5 questions
- Class Autoloading5 questions
- Code Metadata & Introspection11 questions
- Attribute Declarations6 questions
- Reflection & Lazy Objects5 questions
- Errors & Exceptions17 questions
- Throwable Class Tree5 questions
- Catching & Rethrowing6 questions
- Handlers & Reporting Levels6 questions
- Serving HTTP Requests27 questions
- Superglobals & Raw Input6 questions
- Form Submissions5 questions
- Receiving Uploaded Files5 questions
- Headers & Output Buffering5 questions
- Cookies & Sessions6 questions
- Secure Coding26 questions
- Validation & Filtering6 questions
- Output Escaping6 questions
- Dangerous Sinks5 questions
- Password & Random APIs5 questions
- CSRF Tokens4 questions
- Files & Streams15 questions
- Reading & Writing5 questions
- Directories & Permissions5 questions
- Wrappers & Contexts5 questions
- Data Formats23 questions
- CSV Rows & Quoting5 questions
- JSON Encode & Decode6 questions
- XML Parsing6 questions
- Serialized PHP Values6 questions
- Database Access25 questions
- PDO Setup & Fetch Modes5 questions
- Prepared Statements5 questions
- Commit & Rollback Flow5 questions
- MySQLi Extension6 questions
- Persistent Connections4 questions
- Outbound HTTP10 questions
- Client URL Library5 questions
- Guzzle Client5 questions
- Host OS Interaction20 questions
- Shell Commands4 questions
- Forking & Signals5 questions
- Env Vars & Config Files5 questions
- CLI Scripts6 questions
- Concurrency Options10 questions
- Fibers & Suspension5 questions
- Long-Running Workers5 questions
- Standard Library Extras11 questions
- Dates & Time Zones5 questions
- SPL Structures & Iterators6 questions
- Speed & Footprint22 questions
- OPcache & JIT6 questions
- Refcounting & Cycle Collection6 questions
- Profiling Techniques5 questions
- APCu & Realpath Cache5 questions
- PHP-FIG Standards22 questions
- Coding Style PSRs5 questions
- HTTP Message Interfaces6 questions
- Logging & Caching PSRs6 questions
- Container, Event & Clock PSRs5 questions
questions
434 · 21 sectionsIn PHP, how do you find which php.ini file is actually loaded, and why might editing php.ini seem to change nothing?
basics
~20 sRun php --ini for the CLI, or call php_ini_loaded_file() or phpinfo() from a web page, since each SAPI may load a different file. php.ini is read at startup, so PHP-FPM or Apache must be restarted after an edit.
In PHP, why does a page-view counter kept in a static variable reset on every request to a website?
basics
~20 sPHP runs every web request share-nothing: variables, statics, globals and objects are created for that request and freed when it ends. A static counter starts from its initial value on each page view, so real counts need an external store.
On a PHP photo-contest site on a shared host, why does ini_set('upload_max_filesize', '20M') fail to raise the upload limit?
basics
~20 supload_max_filesize is changeable only at INI_PERDIR or INI_SYSTEM level, so ini_set() from a script refuses it and returns false. The upload is also parsed before the script runs. Set it in php.ini, .user.ini or .htaccess instead.
Which headline language features did each PHP release from 8.0 to 8.5 introduce?
basics
~20 s8.0: named arguments, attributes, union types, match, nullsafe, promotion, JIT. 8.1: enums, readonly, fibers, first-class callables. 8.2: readonly classes, DNF types. 8.3: typed class constants, #[\Override]. 8.4: property hooks, asymmetric visibility. 8.5: pipe operator, clone-with.
When upgrading a ten-year-old PHP 7.4 invoicing application to PHP 8.5, which changes are most likely to break it, and how do you sequence the work?
basics
~20 sMost breakage comes from 8.0: 0 == "" is false, many warnings became TypeError, count(null) throws, PDO throws by default. Then come later deprecations. Add tests, fix on 7.4, run 8.x in CI, walk the migration guides, then switch.
In PHP 8, what are the differences between a switch statement and a match expression?
basics
~20 sswitch is a statement that compares loosely (==) and falls through until a break. match, since PHP 8.0, is an expression that compares strictly (===), never falls through, returns a value and throws UnhandledMatchError when nothing matches.
In PHP 8, what is the difference between the == and === operators, and why do most codebases default to ===?
basics
~20 s== compares values after type juggling, so an int and a string can be equal; === is true only when type and value both match. Loose rules still surprise after PHP 8.0, so === is the safe default.
In PHP, what are the differences between echo and print, and why is neither of them a function?
basics
~10 sBoth output strings and are language constructs, not functions. echo accepts several comma-separated arguments and returns nothing; print accepts one argument and always returns 1, so it can appear inside an expression.
Why do PHP style guides say to omit the closing ?> tag in files that contain only PHP code?
basics
~20 sAnything after a closing ?> is output, and PHP swallows only the one newline right after the tag. A stray blank line in an included class file then corrupts responses or blocks later headers; omitting ?> removes the risk.
In PHP, why can't a helper function read a config variable defined at the top of the script, and how should it get the value?
basics
~20 sPHP has function scope and global scope, and a function sees only its own variables, so an outer $config is undefined inside it. Pass the value as a parameter; global and $GLOBALS work but hide the dependency.
In PHP, what does a nullable type such as ?Customer mean, and is it the same as Customer|null or an optional parameter?
basics
~20 s?Customer accepts a Customer object or null, and is exactly the same type as Customer|null. Nullable does not mean optional: the argument must still be passed unless the parameter also has a default, written ?Customer $c = null.
In PHP, what does declare(strict_types=1) change about how scalar parameter and return type declarations are checked?
basics
~20 sBy default PHP coerces a wrong-typed scalar into the declared type when it can, so "12" passed to an int parameter becomes 12. With declare(strict_types=1) the value must already match, or a TypeError is thrown; only int-to-float widening survives.
In PHP, which values convert to false in a boolean context, and why does the string '0' cause bugs?
basics
~20 sfalse, 0, 0.0, -0.0, '', '0', an empty array and null are falsy; everything else is true. '0' is the trap: a real value such as a zero quantity or the name '0' fails an if check.
In PHP, what does an (int) cast return for strings such as '42', ' 42 ', '42abc', '1e3', '0x1A' and 'abc'?
basics
~20 s(int) takes the leading numeric part of a string and never fails: '42', ' 42 ' and '42abc' give 42, '1e3' gives 1000, while '0x1A' and 'abc' give 0. No warning is raised for the leading-numeric or non-numeric cases.
In PHP, why is 0.1 + 0.2 == 0.3 false, and how should you compare two floats?
basics
~20 sPHP floats are IEEE 754 binary doubles, and 0.1, 0.2 and 0.3 have no exact binary form, so the sum is 0.30000000000000004. Compare floats with a tolerance, abs($a - $b) < $epsilon, or avoid floats where exact decimals matter.
In PHP 8.5, which values count as a callable, and which older callable forms are deprecated?
basics
~20 sA PHP callable is a function-name string, a 'Class::method' string, an [object or class, 'method'] array, a Closure, or an invokable object. Since PHP 8.2, relative forms such as 'self::method' or ['parent', 'method'] are deprecated.
In PHP, how does an anonymous function reach variables of the enclosing scope through use, and what changes with use (&$x)?
basics
~20 sA function () closure sees no outer variables unless listed in use. use ($x) copies the value when the closure is created; use (&$x) binds the variable itself, so later changes flow both ways between closure and outer scope.
In PHP, what does calling a function that contains yield return, and when does the code in its body actually run?
basics
~20 sA function containing yield returns a Generator object when called, without running its body. Each foreach pull runs the body to the next yield, which hands out a value and pauses with the locals intact.
In PHP, how do default parameter values work, and what happens when an optional parameter is declared before a required one?
basics
~20 sA default applies only when the caller omits that argument; an explicit null is passed as null. Defaults must be constant expressions. An optional parameter declared before a required one is deprecated since PHP 8.0 and treated as required.
In PHP, how do fn arrow functions capture outer variables compared with function () use (), and what can an arrow function not do?
basics
~20 sAn fn arrow function (PHP 7.4+) is a one-expression closure that captures every outer variable its body uses, automatically and by value. It has no use clause, so it cannot write to outer variables or contain statements.
In PHP, how do you destructure an array into separate variables with [] or list(), both by position and by key?
basics
~20 sPut an array pattern on the left of =: [$lat, $lng] = $point copies the elements at keys 0 and 1, and ['lat' => $lat] = $point copies by key. list() is the older spelling of the same construct.
In PHP, what is the difference between array_merge() and the + union operator when both arrays share keys?
basics
~10 sarray_merge() lets later arrays overwrite earlier ones for string keys but appends and renumbers integer keys; $a + $b keeps every key of $a, adds only keys missing from $a, and never renumbers.
In PHP, if you assign an array to another variable or pass it to a function and modify that copy, what happens to the original?
basics
~20 sNothing: PHP arrays are values, so assignment and by-value parameters give an independent copy, and changing it leaves the original intact. Objects stored inside are handles, so both copies still point to the same objects.
In PHP, what does it mean that an array is an ordered map, and how do indexed and associative arrays differ?
basics
~20 sA PHP array is one ordered map from int or string keys to values of any type. Indexed and associative arrays are the same type: an indexed array just has keys 0, 1, 2, and every array keeps insertion order.
In PHP, what is the difference between sort(), asort() and ksort(), and which of them keep an array's keys?
basics
~10 ssort() orders the values and renumbers the keys 0, 1, 2; asort() orders the values but keeps each key with its value; ksort() orders by key. All three sort in place and return true.
In PHP, how do explode(), implode() and trim() behave when you split a comma-separated list of invoice codes, and which edge cases bite?
basics
~20 sexplode(',', $s) splits on a separator and returns an array, implode(',', $parts) joins it back, and trim() strips whitespace. Traps: an empty string explodes to [''], an empty separator throws ValueError, and trim's second argument is a set of characters.
In PHP, why is if (strpos($code, 'INV')) a bug, and how should you test whether a string contains a substring?
basics
~20 sstrpos() returns the byte offset of the first match or false, and a match at offset 0 is falsy, so a plain if treats it as not found. Compare with !== false, or call str_contains(), added in PHP 8.0.
In PHP, why does strlen('日本語') return 9, and how do you count the characters of a UTF-8 string instead?
basics
~20 sA PHP string is a sequence of bytes with no encoding attached, and strlen() counts bytes; each of those three kanji takes three bytes in UTF-8. mb_strlen($s, 'UTF-8') decodes the bytes and counts characters, returning 3.
In PHP, what is the difference between single-quoted and double-quoted strings?
basics
~10 sSingle-quoted strings are literal: only ' and \ are escapes and variables are not expanded. Double-quoted strings interpret escape sequences such as \n, \t and \u{...} and interpolate variables like $name.
In PHP, why does a preg_* pattern need delimiters such as /…/ or #…#, and what do the i, u, x and s modifiers change?
basics
~20 sDelimiters mark where a PHP pattern ends so modifiers can follow, as in '/ord-\d+/i'. i ignores case, u treats pattern and subject as UTF-8, x ignores pattern whitespace and allows comments, and s lets the dot match newlines.
In PHP, what is __construct, when does it run, and what does PHP 8 no longer treat as a constructor?
basics
~20 s__construct is the method new calls on every freshly created object, receiving the arguments written after the class name. A class has at most one. Since PHP 8.0 a method named after the class is no longer a constructor.
In PHP 8.1 and later, why use an enum instead of class constants for a fixed set of values such as order statuses?
basics
~20 sAn enum is a real type with a closed set of cases, so a parameter typed OrderStatus accepts only those cases and rejects any string. Class constants are plain strings or ints that any typo or foreign value can impersonate.
In PHP, what do the abstract and final modifiers mean on a class and on a method, and why can one member not be both?
basics
~20 sAn abstract class cannot be instantiated and an abstract method has no body, so a concrete child must supply it. A final class cannot be extended and a final method or class constant cannot be overridden. Combining them is contradictory, so PHP rejects it.
In PHP, how does a child class override an inherited method, and how does it still run the parent's version with parent::?
basics
~20 sA PHP class extends exactly one parent and overrides a method by declaring one with the same name and a compatible signature. Inside it, parent::name() runs the inherited body on the same object; a child constructor must call parent::__construct() itself.
In PHP, are objects passed by reference, and what really happens when you assign an object or pass it to a function?
basics
~20 sNo. A PHP object variable holds an object handle, and assignment, argument passing and return copy that handle, so both sides reach one instance. Rebinding a parameter never affects the caller; only & creates a true alias.
In PHP, what does spl_autoload_register() do, and what happens when code uses a class that is not yet defined?
basics
~20 sspl_autoload_register() adds a callback to PHP's autoloader queue. When code uses an undefined class, PHP calls each callback in order with the class name until one defines the class; if none does, an Error "Class not found" is thrown.
In a PHP file declaring namespace App\Billing, how do the class names Invoice, Tax\Rate and \Invoice each resolve?
basics
~10 sInvoice becomes App\Billing\Invoice unless a use statement imports that short name; Tax\Rate becomes App\Billing\Tax\Rate unless Tax is an imported alias; \Invoice is absolute and means the global class Invoice.
In PHP 8.5, what happens when include versus require cannot open the file, and which should load a bootstrap file?
basics
~20 sWhen the file cannot be opened, include emits warnings, returns false and the script continues; require emits a warning and then throws an Error, which stops the script unless caught. Anything the program cannot run without should use require.
Under PSR-4, with the prefix Acme\Blog\ mapped to src/, which file must hold Acme\Blog\Http\PostController, and what must match?
basics
~10 sThe file is src/Http/PostController.php: the prefix Acme\Blog\ is replaced by src/, remaining namespace segments become directories and the class name becomes the file name, all matching the class name's letter case.
In namespaced PHP code, why does calling strlen() work without an import while new DateTime() fails with a class-not-found Error?
basics
~20 sAn unqualified function or constant name that is not imported falls back to the global one at run time if no namespaced version exists; class names never fall back, so DateTime inside App means App\DateTime.
In PHP, how do you declare a custom attribute class such as #[Route] and read it back from controller methods at run time?
basics
~10 sWrite an ordinary class marked #[Attribute] whose constructor takes the attribute's arguments, put #[Route('/users')] on a method, then call getAttributes(Route::class) on its ReflectionMethod and newInstance() on each ReflectionAttribute returned.
In PHP, how does a tiny dependency-injection container use ReflectionClass and getParameters() to autowire a class's constructor dependencies?
basics
~20 sFor a requested class it calls getConstructor(), walks getParameters(), and for each parameter whose type is a ReflectionNamedType naming a class, resolves that class recursively; scalars need a default or explicit config. It then calls newInstanceArgs() with the resolved list.
In PHP 8, how do native attributes written as #[...] differ from docblock annotations such as @Route inside a /** */ comment?
basics
~20 sAttributes are real PHP syntax: the engine parses them, resolves their names like class names and returns them through Reflection's getAttributes(). Docblock annotations are comment text a userland library must fetch with getDocComment() and parse itself.
In PHP, how do you create an object when its class name is only known at run time, and when is ReflectionClass::newInstanceArgs() actually needed?
basics
~20 sPlain new already accepts a variable: new $class(...$args), with string keys passed as named arguments. ReflectionClass::newInstanceArgs() does the same; reflection is needed when you must inspect the class first, or build it without its constructor or lazily.
In PHP 8.2 and later, what does the #[\SensitiveParameter] attribute redact, and what does it leave exposed?
basics
~20 sIt replaces the marked argument with a SensitiveParameterValue object in every backtrace PHP builds, so exception traces and debug_backtrace() no longer print it. The function body, var_dump(), logging and callees without the attribute still see the real value.
In PHP, how do try, catch and finally blocks work together, and in what order are multiple catch blocks tried?
basics
~20 sCode in try runs until something throws; PHP then tests catch blocks top to bottom and runs the first whose type matches the thrown object. finally runs afterwards, whether the try succeeded, was caught, or is still propagating.
In PHP 8, what is the difference between the Error and Exception classes, and why does catch (Exception $e) miss a TypeError?
basics
~20 sBoth implement the Throwable interface but sit on separate branches: Error covers faults PHP itself detects, like TypeError or DivisionByZeroError, while Exception covers failures application and library code reports. catch (Exception) never matches an Error subclass.
In PHP, how do you wrap a failed payment-provider call in your own domain exception without losing the original cause?
basics
~20 sCatch the provider client's exception narrowly and throw your own exception class, passing the caught object as the third constructor argument, $previous. Callers depend only on your type, while getPrevious() still returns the provider's original failure for logs and debugging.
In PHP, what is the difference between the display_errors and log_errors settings, and how should each be set in production?
basics
~20 sdisplay_errors writes error messages into the script's output, where visitors see them; log_errors writes them to the error log. Production should run display_errors=Off and log_errors=On, the values php.ini-production ships, so errors are recorded but never shown.
How do you turn PHP warnings and notices into exceptions with set_error_handler and ErrorException?
basics
~20 sRegister a callback with set_error_handler() that throws new ErrorException($errstr, 0, $errno, $errfile, $errline). Warnings and notices then become catchable exceptions; the callback should first skip levels excluded by error_reporting(), and fatal or compile-time errors never reach it.
In PHP, how do HTML form field names become $_POST keys, and what do name[] and name[key] produce?
basics
~20 sEach submitted control's name attribute becomes a $_POST key holding a string. A name ending in [] appends to a list, name[key] sets that key, and a repeated plain name keeps only its last value, so multi-selects need name[].
In PHP, what causes the "Cannot modify header information - headers already sent" warning, and how do you fix it?
basics
~20 sHeaders are sent with the first byte of body output, so header() fails with that warning once anything has been output: an echo, whitespace outside the PHP tags, a BOM or a displayed warning. Send headers before any output.
In PHP, why must header('Location: ...') be followed by exit, and which status code does the redirect send?
basics
~20 sheader('Location: ...') only queues a header; the script keeps running, so code after it still executes and its output is sent. Call exit right after. PHP adds a 302 unless a code is given, so pass 303 or 301 explicitly.
In PHP, what does session_start() actually do, and how does $_SESSION data survive from one request to the next?
basics
~20 ssession_start() takes the session ID from the PHPSESSID cookie or creates a new one, loads that ID's stored data through the save handler into $_SESSION, and PHP writes $_SESSION back to storage when the request ends.
In PHP, how do setcookie() and $_COOKIE work together, and why is a cookie you just set missing from $_COOKIE?
basics
~20 ssetcookie() queues a Set-Cookie response header, so it must run before output; $_COOKIE holds only what the browser sent with the current request. A cookie set now appears in $_COOKIE on the next request, not this one.
In a plain PHP app, how do you protect a bank's change-email form with a CSRF token, from generating it to checking it on POST?
basics
~20 sGenerate an unpredictable token (bin2hex(random_bytes(32))), store it in $_SESSION, and echo it in a hidden field of the change-email form. On POST, compare the submitted field against the session copy with hash_equals(); if it is missing or does not match, reject the request.
In PHP, how do you safely echo a user's review into an HTML page with htmlspecialchars(), and which flags and charset should you pass?
basics
~20 sWrap every untrusted value in htmlspecialchars() at the moment it is echoed. Pass ENT_QUOTES | ENT_SUBSTITUTE and 'UTF-8', the PHP 8.1+ defaults, so both quote types are encoded and malformed UTF-8 cannot turn the output into an empty string.
In PHP, how do you validate an age field with filter_var() and FILTER_VALIDATE_INT, and how do you detect a failed check correctly?
basics
~10 sCall filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 16, 'max_range' => 99]]). It returns an int on success and false on failure, so test with === false, because a valid 0 is falsy.
In PHP, what do password_hash() and password_verify() do, and what exactly should you store in the database?
basics
~20 spassword_hash($plain, PASSWORD_DEFAULT) returns a self-describing string that embeds the algorithm, cost and a random salt; store that one string. password_verify($plain, $hash) rehashes the input the same way and returns a bool. Never generate the salt yourself or compare hashes manually.
In PHP, which built-in calls turn attacker-controlled request data into executed code or instantiated objects, and why are they dangerous?
basics
~10 seval() runs a string as PHP; include/require execute a file you supply; unserialize() rebuilds objects and calls their magic methods; extract() writes request keys into variables. Never feed request data to any of them.
In PHP, what goes wrong with mkdir($dir, 755, true), and how do you create a directory tree with the permissions you intend?
basics
~20 s755 without a leading zero is a decimal number, octal 1363, so the directory gets scrambled permissions. Write 0755 (or 0o755 since PHP 8.1), pass true to create parents, expect the umask to trim the mode, and treat an existing directory as a false return.
In PHP, when do you use file_get_contents() and file_put_contents() instead of fopen() with fgets() and fwrite(), and what does each approach cost?
basics
~20 sfile_get_contents() and file_put_contents() read or write a whole file in one call, so the entire content sits in memory. fopen() returns a handle for fgets(), fread() and fwrite(), keeping memory flat for large or streamed files.
In PHP, why can is_file() or filesize() keep returning an old result inside one script, and when is clearstatcache() actually needed?
basics
~20 sPHP caches the last stat() result, and is_file(), filesize(), filemtime() and similar functions reuse it for the same path. If another process changes that file, repeated checks in one script can see old data until clearstatcache() is called.
In PHP, what is the difference between php://memory and php://temp, and how would you use one to buffer a generated report before sending it?
basics
~20 sphp://memory keeps all data in RAM, limited only by memory_limit; php://temp keeps up to 2 MB in memory, then moves to a temporary file. Write the report into php://temp, rewind(), then stream it out with fpassthru() or stream_copy_to_stream().
In PHP, how do you process a multi-gigabyte log file line by line without hitting memory_limit, and which loop mistakes still break it?
basics
~20 sOpen the file with fopen() and loop while fgets() does not return false, or iterate an SplFileObject, so only one line is in memory. file() and file_get_contents() load everything, and collecting results in an array grows memory again.
In PHP, what does json_decode() return for a JSON object when its associative argument is omitted, and what changes when it is true?
basics
~10 sBy default json_decode() turns every JSON object into a stdClass object, read with ->; with the associative argument true, objects become associative arrays, read with []. JSON arrays become PHP arrays in both modes.
In PHP, how do you read an element's text and an attribute with SimpleXML, and why do you cast the results to string?
basics
~20 ssimplexml_load_string() returns a SimpleXMLElement: child elements are read as properties ($p->name) and attributes with array syntax ($p['sku']). Both return SimpleXMLElement objects, so cast with (string), (int) or (float) before comparing, storing or passing them on.
In PHP, how do you read a multi-gigabyte CSV file with fgetcsv() without exhausting memory, and how do you detect the end of the file?
basics
~20 sOpen the file with fopen() and call fgetcsv() in a loop until it returns false; each call parses one record, so memory stays flat. A blank line returns [null], not false, so skip it explicitly.
In PHP, why is comparing json_decode()'s result with null an unreliable error check, and what does JSON_THROW_ON_ERROR change?
basics
~10 sjson_decode() returns null both for invalid input and for the valid JSON text null, so a null check cannot tell them apart. JSON_THROW_ON_ERROR makes json_decode() and json_encode() throw JsonException instead of setting json_last_error().
In PHP, when should you store a value with serialize() rather than json_encode(), and what does each round trip lose?
basics
~20 sUse serialize() only for PHP-to-PHP storage you control, because it restores exact types, classes, private properties and shared references. Use json_encode() for anything another program or an untrusted party touches: portable and inert, but classes and non-public state are lost.
In PHP, what are the practical differences between the mysqli extension and PDO, and when would you choose mysqli?
basics
~20 smysqli works only with MySQL, offers procedural and object styles, positional ? placeholders and MySQL-specific features such as multi_query(). PDO is one object API across many databases with named placeholders. Choose mysqli for existing mysqli code or MySQL-only features.
In PHP, how do you open a PDO connection to MySQL, and what should its options array set?
basics
~10 sCall new PDO($dsn, $user, $password, $options) with a DSN such as mysql:host=db;dbname=clinic;charset=utf8mb4. Set PDO::ATTR_ERRMODE to ERRMODE_EXCEPTION and PDO::ATTR_DEFAULT_FETCH_MODE to FETCH_ASSOC; a failed connection throws PDOException.
In PHP, how do PDO::prepare() and execute() run a query with user input, and how do ? and :name placeholders differ?
basics
~20 sPDO::prepare() turns an SQL template with placeholders into a PDOStatement; execute() supplies the values, a list for ? markers or a keyed array for :name markers. The styles cannot be mixed, and a marker is one whole value.
In PHP, what do PDO's ERRMODE_SILENT, ERRMODE_WARNING and ERRMODE_EXCEPTION do, and why did PHP 8.0's default change matter?
basics
~20 sSILENT makes failing calls return false and leaves details in errorInfo(); WARNING also emits E_WARNING; EXCEPTION throws PDOException. PHP 8.0 made EXCEPTION the default, so a failed query no longer slips by as an unchecked false.
In PHP's PDO, how does bindParam() differ from bindValue(), and when does the PDO::PARAM_* type argument change what the database receives?
basics
~20 sbindValue() copies a value when called; bindParam() binds a variable by reference and reads it at execute(). The PARAM_* type, PARAM_STR by default, decides how the value is sent, which matters for LIMIT, booleans and binary data.
In PHP's cURL extension, what does CURLOPT_RETURNTRANSFER change about curl_exec(), and how do you tell a failed transfer from an HTTP error?
basics
~20 sCURLOPT_RETURNTRANSFER makes curl_exec() return the body as a string instead of printing it. curl_exec() returns false only when the transfer itself fails (read curl_errno() and curl_error()); a 404 or 500 still succeeds, so check CURLINFO_RESPONSE_CODE.
When you build a Guzzle Client with base_uri and default options, how are relative paths resolved, and what timeout applies if you set none?
basics
~20 sGuzzle resolves a request URI against base_uri by RFC 3986: 'rates' after 'https://api.test/v2/' gives /v2/rates, but '/rates' or a base without the trailing slash drops v2. The timeout option defaults to 0, which waits indefinitely.
In PHP's cURL extension, how do CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT differ, and what happens when neither is set?
basics
~20 sCURLOPT_CONNECTTIMEOUT caps only connection setup (default 300 seconds); CURLOPT_TIMEOUT caps the whole transfer (default 0, meaning never). Without both, a hanging API can hold a PHP worker for minutes; set a short connect timeout and a total timeout.
In Guzzle 7, what does the http_errors option control, and which exceptions do a 404, a 503 and a timed-out request raise?
basics
~20 sWith http_errors true (the default), a 404 throws ClientException and a 503 ServerException, both BadResponseExceptions carrying the response. A timeout or refused connection throws ConnectException, which since Guzzle 7 extends TransferException, not RequestException, and has no response.
How do you unit-test code that uses a Guzzle client with MockHandler, and why wrap it in HandlerStack::create()?
basics
~20 sInject a Client whose handler is HandlerStack::create(new MockHandler([...])); queued responses and exceptions are returned in order without network access. The stack matters because http_errors and other options are middleware; a bare MockHandler never throws on 4xx or 5xx.
In a PHP CLI script, what do $argv and $argc contain, and why can a function not see $argv directly?
basics
~20 s$argv is an array of the command-line arguments with the script name at index 0, and $argc is count($argv). Both are ordinary global variables, not superglobals, so inside a function use $_SERVER['argv'] or pass the array in.
In PHP, how do getenv() and putenv() work, and what does getenv() return for a variable that is not set?
basics
~20 sgetenv('NAME') returns the variable's value as a string, or false when it is not set; getenv() with no argument returns all of them as an array. putenv('NAME=value') sets a variable for the current process and its children until the request ends.
In PHP, how do exec(), shell_exec(), system() and passthru() differ in what they return, what they print and how they report the exit code?
basics
~20 sexec() returns the last output line, appends every line to $output and sets $result_code. shell_exec() returns the whole output but no exit code. system() prints output and returns the last line; passthru() streams raw bytes.
In PHP, how do escapeshellarg() and escapeshellcmd() differ, and which one should wrap a user-supplied filename passed to exec()?
basics
~20 sescapeshellarg() wraps one value in single quotes so the shell sees exactly one literal argument; use it on every dynamic argument. escapeshellcmd() backslash-escapes metacharacters in a whole command but leaves spaces and paired quotes, so injected extra arguments survive.
What do the PHP CLI options -r, -l and -a do, and how does a shebang line make a PHP script runnable as ./import?
basics
~10 sphp -r runs inline code without <?php tags; php -l only checks syntax; php -a opens an interactive shell that needs readline. A first line #!/usr/bin/env php plus chmod +x makes ./import runnable.
In PHP, if you start two Fibers that each run a CPU-heavy loop or call sleep(), do they run in parallel, and why?
basics
~20 sNo. All fibers in a PHP process share one thread and only one runs at a time; control moves only when code calls Fiber::suspend(), start(), resume() or throw(). A CPU loop or sleep() inside one fiber stalls all the others.
After moving a PHP API to worker mode, some responses show another user's data; what state is leaking between requests, and how do you find and fix it?
basics
~20 sRequest data, typically the authenticated user, was stored in something that outlives the request: a static property, a static function variable or a memoised singleton. The next request on that worker reuses it. Make such state request-scoped or reset it after each request.
In PHP, what is a Fiber, and how do Fiber::start(), Fiber::suspend() and Fiber::resume() pass values between the fiber and its caller?
basics
~20 sA PHP Fiber (core since 8.1) runs a callable on its own call stack that can pause. start() runs it until Fiber::suspend($x), which makes start() return $x; resume($y) continues it and makes that suspend() call return $y.
In PHP, which Fiber misuses throw FiberError, and what happens to an exception thrown inside a fiber or injected with Fiber::throw()?
basics
~10 sFiberError, an Error subclass, marks wrong-state calls: starting twice, resuming a non-suspended fiber, Fiber::suspend() outside a fiber, an early getReturn(). Exceptions inside a fiber leave through the start(), resume() or throw() that entered it.
In PHP, how does a Fiber differ from a Generator built with yield, and why can a Fiber pause from inside deeply nested function calls?
basics
~20 sA Generator pauses only at a yield in its own body, so every caller in between must also yield and return a Generator. A Fiber has its own call stack, so Fiber::suspend() pauses from any depth with callers unchanged.
In PHP, what is the difference between DateTime and DateTimeImmutable, and what bug does calling modify() on a shared DateTime cause?
basics
~20 sDateTime's modify(), add(), sub(), setDate() and setTimezone() change the object itself and return it; DateTimeImmutable's return a new object and leave the original alone. So $end = $start->modify('+7 days') on a DateTime silently moves $start too.
In PHP, how do you make your own collection class usable in foreach, and when do you implement Iterator versus IteratorAggregate?
basics
~20 sImplement IteratorAggregate and return an iterator such as new ArrayIterator($this->items) from getIterator(), or implement Iterator's five cursor methods yourself when traversal needs custom logic. Both extend Traversable, which foreach recognises; one class cannot implement both.
In PHP, how do the default time zone, DateTimeZone and setTimezone() decide what a date object shows, and how should apps store and display times?
basics
~20 sEvery PHP date object has a zone: the one passed in, else date_default_timezone_set(), else the date.timezone ini value, else UTC. setTimezone() keeps the instant and changes only the wall-clock view. Store UTC plus the user's zone ID.
In PHP, how does DateTimeImmutable::createFromFormat() parse input, and why can a date like 2026-02-30 or a format without '!' give a surprising result?
basics
~20 screateFromFormat() parses against an explicit format and returns false when it cannot. Fields the format omits take the current time unless it starts with ! or ends with |, and February 30 rolls into March with only a warning.
In PHP, what does DateTimeImmutable::diff() return, why does $interval->format('%d') often give the wrong day count, and how does DatePeriod iterate a range?
basics
~20 sdiff() returns a DateInterval split into years, months, days and time, plus days, the total day count. %d prints only the days component; %a prints the total. DatePeriod steps from start to end, excluding the end unless INCLUDE_END_DATE is set.
In PHP, what does the fatal error "Allowed memory size of 134217728 bytes exhausted" mean, and what does memory_limit control?
basics
~20 sThe script asked PHP's memory manager for more than memory_limit allows (134217728 bytes is the default 128M), so PHP stopped with a fatal error that try/catch cannot catch. memory_limit caps one process's engine allocations; -1 removes it.
In PHP, what does OPcache do for a web application, and what does it not cache?
basics
~20 sOPcache stores each script's compiled opcodes in shared memory, so later requests skip reading and compiling the PHP source and run the cached opcodes directly. It caches code only: not query results, not rendered pages, not application data.
In PHP, when does caching a feature-flag list in APCu on each web server beat Redis, and when does it give wrong answers?
basics
~20 sAPCu wins for small, read-heavy, rarely changing data such as a flag list: a local memory read, no network hop. It gives wrong answers when servers must agree, because each server holds its own copy and cannot invalidate the others.
In PHP, what do opcache.validate_timestamps and opcache.revalidate_freq control, and why do production servers often disable validation?
basics
~20 sWith opcache.validate_timestamps=1 (the default) OPcache checks a cached script's modification time at most every opcache.revalidate_freq seconds (default 2) and recompiles it if it changed. Production often sets 0: no file checks at all, so a deploy must reset OPcache.
After a release, a PHP product page is 800 ms slower in production; how do you find the slow path instead of guessing at it?
basics
~20 sConfirm and scope the regression, split wall time from CPU time, narrow it with hrtime spans around each phase, then profile production traffic with a low-overhead sampling profiler on a small fraction of requests and compare against the previous release.
In PSR-11, what do ContainerInterface::get() and has() promise, and what does get() throw for an unknown identifier?
basics
~10 sPSR-11's ContainerInterface has get($id), returning any entry, and has($id), returning a bool. If has() returns false, get() must throw Psr\Container\NotFoundExceptionInterface; other container errors implement ContainerExceptionInterface.
In PSR-3, what are the eight log levels in Psr\Log\LogLevel, and how do you choose between them?
basics
~20 sPSR-3 defines eight RFC 5424 levels: emergency, alert, critical, error, warning, notice, info and debug, each with its own LoggerInterface method. Choose by urgency: from system unusable, through failures and oddities, down to routine events and diagnostics.
In PHP, what are the PSR-1 and PSR-12 coding standards, and what kinds of rules does each one set?
basics
~20 sPSR-1 is PHP-FIG's basic coding standard: tags, UTF-8, naming, one class per autoloadable file, and no mixing of declarations with side effects. PSR-12 extends it with formatting: indentation, line endings, braces, spacing and file-header order, replacing PSR-2.
In PSR-15, what do MiddlewareInterface::process() and RequestHandlerInterface::handle() declare, and how does a rate-limiting middleware reject or delegate a request?
basics
~10 sPSR-15 declares handle(ServerRequestInterface $request): ResponseInterface and process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface. A rate limiter returns its own 429 response to reject, or calls $handler->handle($request) and returns that response.
In PSR-7, why does calling $response->withHeader('X-Trace-Id', $id) without assigning the result leave the response unchanged?
basics
~10 sPSR-7 messages are immutable: withHeader() returns an instance carrying the change and must leave the original untouched. Discarding the return value discards the change; write $response = $response->withHeader(...) or return the new instance.