Interview Q&A · 102 questions · 25 answered publicly
PHP 8 Interview Questions
Ordered by how often they're asked. Sign in free to see every answer, rate your confidence and run timed mock interviews.
-
Most asked What are the most important features introduced in PHP 8.0?
Summary
PHP 8.0 added the JIT compiler, union types, named arguments, attributes, constructor property promotion, the match expression, the nullsafe operator and much stricter, more consistent error handling.
Longer Answer
PHP 8.0 (November 2020) was a major release focused on type safety and expressiveness. The language gained union types (
int|string), themixedandstaticreturn types, named arguments, attributes (native metadata replacing docblock annotations), constructor property promotion, thematchexpression, and the nullsafe operator (?->).Under the hood it shipped the JIT (tracing and function modes on top of OPcache), turned many warnings into
TypeError/ValueErrorexceptions for internal functions, and changed string-to-number comparison semantics so0 == "foo"is nowfalse.Key Points
- Union types,
mixed,staticreturn type - Named arguments and constructor promotion reduce boilerplate
- Attributes give first-class, reflectable metadata
matchis a strict, expression-basedswitch- Saner string/number comparisons and consistent internal-function errors
- Union types,
-
Most asked What are union types in PHP 8 and how do you use them?
Summary
Union types let a parameter, return value or property accept one of several types, written
TypeA|TypeB, and are enforced by the engine at runtime.Longer Answer
Before PHP 8 you could only document "int or string" in a docblock. Union types make that a real contract the engine checks. Any combination of class/interface names and scalar types is allowed, plus
null(so?Foois shorthand forFoo|null) and the specialfalsepseudo-type (PHP 8.2 also allows standalonetrue,falseandnull).Redundant types are compile-time errors (
int|int), andvoidcannot be part of a union. Under coercive typing mode, PHP tries the types in a defined preference order (int, float, string, bool) when coercing scalars.Code Example
<?php declare(strict_types=1); function formatId(int|string $id): string { return is_int($id) ? sprintf('#%05d', $id) : strtoupper($id); } echo formatId(42); // #00042 echo formatId('ab-1'); // AB-1Key Points
- Syntax:
int|string,Foo|Bar|null ?Tis equivalent toT|nullvoidcan never be in a union- Prefer narrow unions; a wide union often signals a missing abstraction
- Syntax:
-
Most asked What are named arguments and what are their benefits and risks?
Summary
Named arguments let callers pass values by parameter name (
fn(name: 'x')), so they can skip optional parameters and make call sites self-documenting.Longer Answer
With named arguments the order no longer matters and you only pass what you need, which is ideal for functions with many optional flags like
htmlspecialchars($s, double_encode: false). They can be combined with positional arguments, but positional ones must come first.The risk: parameter names become part of your public API. Renaming a parameter in a library is now a breaking change for callers using named arguments. Child classes that rename parameters can also cause errors when the call goes through the parent signature. You can also spread string-keyed arrays:
foo(...['b' => 2]).Code Example
<?php function createUser(string $email, bool $admin = false, bool $notify = true, ?string $locale = null) { /* ... */ } // Before: createUser('[email protected]', false, false, 'de'); createUser('[email protected]', notify: false, locale: 'de'); echo htmlspecialchars('<a>&', double_encode: false);Key Points
- Skip optional parameters without passing defaults
- Self-documenting call sites (
boolflags become readable) - Parameter names are now part of the public API
- Positional arguments must precede named ones; duplicates are an Error
-
Most asked What is constructor property promotion?
Summary
Constructor promotion lets you declare and assign class properties directly in the constructor signature by adding a visibility modifier to the parameter.
Longer Answer
It removes the classic three-step boilerplate (declare property, declare parameter, assign in body). A promoted parameter with
public,protectedorprivate(and optionallyreadonlyfrom 8.1) automatically becomes a property initialized with the argument.You can mix promoted and normal parameters, and the constructor body still runs after promotion. Restrictions: no promotion in abstract constructors or interfaces, no variadic promotion, and
callablecannot be a promoted type.Code Example
<?php final class Money { public function __construct( public readonly int $amount, public readonly string $currency = 'USD', ) { if ($amount < 0) { throw new InvalidArgumentException('Amount must be >= 0'); } } } $price = new Money(1999); echo $price->currency; // USDKey Points
- Add a visibility modifier to promote a parameter
- Combine with
readonlyfor immutable value objects - Constructor body still runs for validation
- Not allowed for variadic parameters or
callabletypes
-
Most asked How does `match` differ from `switch`?
Summary
matchis an expression that returns a value, uses strict (===) comparison, has no fall-through, and throwsUnhandledMatchErrorif no arm matches.Longer Answer
switchis a statement with loose comparison (==) and implicit fall-through, which is a frequent bug source.matchfixes all three: each arm is a single expression, comparison is strict, and missing cases fail loudly instead of silently doing nothing.Arms can have comma-separated conditions, and
match (true)is a common idiom for range checks.Code Example
<?php $status = 404; $message = match (true) { $status >= 500 => 'Server error', $status === 404, $status === 410 => 'Not found', $status >= 400 => 'Client error', default => 'OK', }; echo $message; // Not foundKey Points
- Expression: assign or return its result
- Strict comparison (
===) - No fall-through, no
breakneeded - Unmatched value throws
UnhandledMatchError - Arms are single expressions (use a function for multi-statement logic)
Comparison / Tradeoffs
Aspect switch match Kind Statement Expression Comparison Loose ==Strict ===Fall-through Yes (needs break)No No match Silently skipped UnhandledMatchError -
Most asked What are enums in PHP 8.1? Explain pure vs backed enums.
Summary
Enums define a closed set of named values as a first-class type. Pure enums have only cases; backed enums map each case to a scalar
intorstringvalue.Longer Answer
Enums replace class constants used as pseudo-enums, giving real type safety: a parameter typed
Statuscan only receive a valid case. Each case is a singleton object, so===comparison works.Backed enums (
enum Status: string) give->value, plusStatus::from($v)(throwsValueErroron unknown input) andStatus::tryFrom($v)(returnsnull). They are ideal for database columns and API payloads. All enums getcases(). Enums can implement interfaces, have methods and constants, and use traits without properties, but cannot have state or be extended.Code Example
<?php enum OrderStatus: string { case Pending = 'pending'; case Paid = 'paid'; case Shipped = 'shipped'; public function label(): string { return match ($this) { self::Pending => 'Awaiting payment', self::Paid => 'Paid', self::Shipped => 'On its way', }; } } $status = OrderStatus::from($row['status']); echo $status->label(); $maybe = OrderStatus::tryFrom('unknown'); // nullKey Points
- Pure:
enum Suit { case Hearts; } - Backed:
enum Status: string { case Active = 'active'; } from()throws,tryFrom()returns null- Cases are singletons; compare with
=== - Enums can have methods and implement interfaces, but no properties
- Pure:
-
Most asked Explain readonly properties (8.1) and readonly classes (8.2).
Summary
A readonly property can be initialized once, from inside the declaring class scope, and never modified afterwards. A readonly class makes every declared property readonly.
Longer Answer
Readonly properties must be typed. They are initialized once (usually in the constructor) and any further write throws an
Error. Note that readonly is shallow: if the property holds an object, that object's internal state can still change.PHP 8.2 added
readonly class, which marks all properties readonly and forbids dynamic properties. To produce a modified copy you write "withers" that construct a new instance. PHP 8.3 allowed re-initializing readonly properties during__clone(), which makes withers usingclonepossible.Code Example
<?php final readonly class Email { public function __construct(public string $value) { if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException("Invalid email: $value"); } } public function withValue(string $value): static { return new static($value); } } $email = new Email('[email protected]'); // $email->value = 'x'; // Error: Cannot modify readonly propertyKey Points
- Must be typed, initialized once from class scope
- Writes after init throw
Error - Readonly is shallow for objects
readonly class(8.2) applies to all properties- 8.3 allows reinitialization inside
__clone()
-
Most asked When would you use an interface, an abstract class, or a trait in PHP?
Summary
Use an interface to define a contract, an abstract class to share base state and partial implementation in an is-a hierarchy, and a trait to reuse code horizontally across unrelated classes.
Longer Answer
Interfaces declare methods (and constants, and since 8.4 properties) with no state; a class can implement many. They're the backbone of dependency inversion.
Abstract classes can hold properties, constructors and concrete methods, but a class can extend only one. Use them when subclasses genuinely share identity and behavior (e.g.,
AbstractController).Traits copy methods/properties into a class at compile time. They're good for cross-cutting helpers but can hide coupling; they're not types, so you can't type-hint a trait.
Key Points
- Interface = contract, multiple allowed, type-hintable
- Abstract class = shared state + partial implementation, single inheritance
- Trait = compile-time code reuse, not a type
- Prefer composition and interfaces over deep inheritance
Comparison / Tradeoffs
Interface Abstract class Trait State No (8.4: hooked props only) Yes Yes Multiple Yes No Yes Type-hintable Yes Yes No -
Most asked How does Composer autoloading work, and what is PSR-4?
Summary
PSR-4 maps a namespace prefix to a base directory so
App\Models\Userloads fromsrc/Models/User.php. Composer generates an autoloader fromcomposer.jsonthatrequires files on first use.Longer Answer
You configure
"autoload": {"psr-4": {"App\\": "src/"}}and includevendor/autoload.php. Composer registers anspl_autoload_registercallback. In production, runcomposer dump-autoload --optimize(or--classmap-authoritative) to generate a class map and avoid filesystem lookups.Code Example
{ "autoload": { "psr-4": { "App\\": "src/" } }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } } }Key Points
- Namespace prefix → directory mapping
vendor/autoload.phpregisters the loader--optimize/-obuilds a classmap for production--classmap-authoritativeskips filesystem fallback entirely
-
Most asked How do you prevent SQL injection in PHP?
Summary
Use prepared statements with bound parameters (PDO or mysqli), never concatenate user input into SQL, and whitelist anything that can't be parameterized such as column names or sort direction.
Longer Answer
With PDO, set
PDO::ATTR_EMULATE_PREPARES => false(for MySQL) so real server-side prepares are used, andPDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION. Identifiers (table/column names,ORDER BYfields) can't be bound, so validate them against an allow-list. ORMs and query builders parameterize for you, but raw expressions (DB::raw,whereRaw) reintroduce risk.Code Example
<?php $pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, ]); $sort = in_array($_GET['sort'] ?? '', ['name', 'created_at'], true) ? $_GET['sort'] : 'name'; $stmt = $pdo->prepare("SELECT id, name FROM users WHERE status = :status ORDER BY $sort"); $stmt->execute(['status' => $_GET['status'] ?? 'active']); $users = $stmt->fetchAll(PDO::FETCH_ASSOC);Key Points
- Prepared statements for all values
- Allow-list identifiers and sort directions
- Exceptions error mode; disable emulated prepares where practical
- Least-privilege DB users
-
Most asked How do you prevent XSS in PHP applications?
Summary
Escape output for its context:
htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')for HTML,json_encodewith hex flags for JS,rawurlencodefor URLs. Add a Content-Security-Policy as defense in depth.Longer Answer
XSS is an output problem: you escape at render time based on where data lands. Templating engines (Twig, Blade
{{ }}) auto-escape HTML by default; raw output ({!! !!},|raw) must be reserved for sanitized HTML (use HTML Purifier for user-supplied rich text). Set cookiesHttpOnlyso scripts can't read session IDs.Code Example
<?php function e(?string $value): string { return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } ?> <p>Hello, <?= e($user->name) ?></p> <script>const user = <?= json_encode($user, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;</script>Key Points
- Escape on output, per context
- Use auto-escaping templates
- Sanitize rich HTML with HTML Purifier
- CSP and
HttpOnlycookies limit impact
-
Most asked How do you protect a PHP app against CSRF and session attacks?
Summary
Use per-session CSRF tokens on state-changing requests,
SameSitecookies,session_regenerate_id(true)on login, and secure cookie flags (Secure,HttpOnly).Longer Answer
CSRF: generate
bin2hex(random_bytes(32)), store it in the session, embed it in forms, and compare withhash_equals().SameSite=Laxblocks most cross-site POSTs. Session fixation: regenerate the ID after authentication or privilege change. Also setsession.use_strict_mode=1andsession.cookie_secure=1, and expire idle sessions server-side.Code Example
<?php session_set_cookie_params(['secure' => true, 'httponly' => true, 'samesite' => 'Lax']); session_start(); $_SESSION['csrf'] ??= bin2hex(random_bytes(32)); if ($_SERVER['REQUEST_METHOD'] === 'POST' && !hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) { http_response_code(419); exit('Invalid CSRF token'); }Key Points
- Token +
hash_equals()for CSRF SameSite=Lax/Strictcookiessession_regenerate_id(true)on loginsession.use_strict_mode=1
- Token +
-
Most asked How should passwords be stored in PHP?
Summary
Use
password_hash()(bcrypt by default, or Argon2id) andpassword_verify(), and callpassword_needs_rehash()on login to upgrade old hashes.Longer Answer
These functions handle salt generation and embed algorithm and cost in the hash string. Never use
md5,sha1or unsaltedhash(). Bcrypt truncates input at 72 bytes; Argon2id (PASSWORD_ARGON2ID) is memory-hard and recommended where available.Code Example
<?php $hash = password_hash($password, PASSWORD_ARGON2ID); if (password_verify($input, $hash)) { if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) { $hash = password_hash($input, PASSWORD_ARGON2ID); // save new hash } }Key Points
password_hash($pw, PASSWORD_DEFAULT)password_verify()is timing-safe- Rehash on login when cost/algorithm changes
- Never use fast hashes like MD5/SHA1 for passwords
-
Most asked What is OPcache and how should it be configured in production?
Summary
OPcache stores compiled opcodes in shared memory so PHP doesn't re-parse scripts on every request. It's the single most important PHP performance setting.
Longer Answer
Recommended production settings:
opcache.enable=1, enoughopcache.memory_consumption(e.g., 256M) andopcache.max_accelerated_filesfor your file count, andopcache.validate_timestamps=0so PHP never stats files, which means you must reset OPcache (reload FPM) on deploy.opcache.interned_strings_buffersaves memory for repeated strings.Code Example
opcache.enable=1 opcache.memory_consumption=256 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=20000 opcache.validate_timestamps=0Key Points
- Caches compiled opcodes in shared memory
validate_timestamps=0in prod + reload on deploy- Size memory and max files for your codebase
- Monitor hit rate with
opcache_get_status()
-
Most asked What happens to an HTTP request in a PHP-FPM application, and what does 'shared nothing' mean?
Summary
nginx passes the request to PHP-FPM over FastCGI; the master assigns it to an idle worker process; the worker runs the entry script (public/index.php) from the top: autoloading, framework bootstrap, routing, controller, response; then all request memory (variables, objects, static properties) is discarded. Nothing in application memory survives to the next request, only OPcache's compiled code, caches, sessions and databases.
Longer Answer
Each worker handles one request at a time, so capacity is workers divided by request time: slow outbound calls exhaust the pool (502/504) even when CPU is idle. Long-running runtimes (Octane, RoadRunner, FrankenPHP worker mode, Swoole) avoid repeated bootstrap but give up shared-nothing isolation, so request state can leak between requests.
Key Points
- FastCGI → worker → script runs from the top.
- All request memory discarded afterwards.
- One request per worker; capacity = workers ÷ time.
- Long-running runtimes trade isolation for speed.
Deep dive: Request Lifecycle course: PHP-FPM and the Shared-Nothing Model
-
Most asked What does the nullsafe operator `?->` do?
Summary
?->short-circuits a method or property chain and returnsnullas soon as the left-hand side isnull, instead of throwing an error.Longer Answer
It replaces nested null checks such as
$user !== null && $user->getAddress() !== null ? ... : null. When any element in the chain evaluates tonull, the rest of the chain is skipped, including argument evaluation.It is read-only: you cannot use it on the left of an assignment (
$a?->b = 1is a compile error), and it cannot be used with references.Code Example
<?php $country = $session?->user?->getAddress()?->country ?? 'Unknown';Key Points
- Returns
nullinstead of erroring on a null receiver - Short-circuits the entire remaining chain
- Not allowed in write context
- Pairs well with
??to supply a default
- Returns
-
Most asked How does the PHP 8 JIT work and when does it improve performance?
Summary
The JIT compiles hot opcodes to native machine code at runtime on top of OPcache. It greatly speeds up CPU-bound work (math, image processing), but gives little benefit to typical I/O-bound web apps.
Longer Answer
PHP normally compiles source to opcodes (cached by OPcache) that the Zend VM interprets. The JIT, built on DynASM, adds a further step: it compiles frequently executed code to native machine code. The tracing JIT (
opcache.jit=tracing, default mode when enabled) profiles hot loops and functions; the function JIT compiles whole functions.Most web requests spend their time waiting on databases, network and I/O, so benchmarks like WordPress or Laravel see small gains. Fractals, ML in PHP and long-running workers benefit far more. PHP 8.4 moved to a new IR-based JIT backend and changed the configuration defaults (
opcache.jit=disablewith a non-zero buffer).Code Example
; php.ini opcache.enable=1 opcache.enable_cli=1 opcache.jit_buffer_size=128M opcache.jit=tracingKey Points
- Requires OPcache; enable via
opcache.jit_buffer_sizeandopcache.jit - Tracing JIT targets hot paths
- Large gains for CPU-bound code, small for I/O-bound apps
- Measure with real workloads before enabling in production
- Requires OPcache; enable via
-
Most asked What changed in PHP 8 error handling for internal functions?
Summary
Many internal functions now throw
TypeErrororValueErrorinstead of emitting warnings and returningnull/false, and many notices were promoted to warnings orErrorexceptions.Longer Answer
In PHP 7,
strlen([])emitted a warning and returnednull. In PHP 8 it throwsTypeError. Invalid values (e.g.array_fill(0, -1, 'x')) throwValueError. Accessing undefined variables went from notice to warning, and attempting to read a property of a non-object is now a warning; writing a property on null throwsError.The
@operator no longer silences fatal errors, and the defaulterror_reportingisE_ALL. This makes bugs surface earlier, but legacy code often hits new exceptions after upgrading.Key Points
TypeErrorfor wrong argument types in internalsValueErrorfor valid type but invalid value@no longer suppresses fatal errors- Default
error_reportingisE_ALL
-
Most asked How did string-to-number comparison change in PHP 8?
Summary
When comparing a number with a non-numeric string, PHP 8 now compares them as strings instead of casting the string to a number, so
0 == 'foo'isfalse(it wastruein PHP 7).Longer Answer
PHP 7 cast
'foo'to0, making0 == 'foo'true, a classic source of security bugs (e.g. in loosein_arrayorswitch). PHP 8's "Saner string to number comparisons" RFC: if the string is numeric, compare as numbers; otherwise cast the number to a string and compare strings.Also, PHP 8 treats leading-numeric strings like
'42abc'more consistently ("Saner numeric strings" RFC): arithmetic on them gives a warning instead of a notice, and non-numeric strings in arithmetic throwTypeError.Key Points
0 == 'foo': PHP 7true, PHP 8false'1' == '01'stilltrue(both numeric)- Affects
switch,in_arrayandarray_searchwithout strict mode - Prefer
===and strict flags anyway
Comparison / Tradeoffs
Expression PHP 7 PHP 8 0 == 'foo'true false '1' == '01'true true 100 == '1e2'true true null == falsetrue true -
Most asked Compare `??`, `?:`, and `?->`.
Summary
??returns the left operand unless it is null/undefined,?:returns the left operand unless it is falsy, and?->safely calls a method or property on a possibly-null object.Longer Answer
$a ?? $bdoesn't emit a warning for undefined variables or keys, making it ideal for$_GET['x'] ?? 'default'.$a ?: $btreats0,'','0',[]andfalseas missing, which is often a bug for numeric input.?->chains through nulls.??=(PHP 7.4) assigns only when null.Key Points
??checks null/undefined only?:checks falsiness (beware0and'0')?->short-circuits on null in chains??=assigns if null
Comparison / Tradeoffs
Operator Falls back when Example ??null or undefined $_GET['page'] ?? 1?:falsy $name ?: 'Anonymous'?->receiver is null $user?->profile -
Most asked What is late static binding?
Summary
Late static binding means
static::resolves to the class that was actually called at runtime, whereasself::always resolves to the class where the method is written.Longer Answer
It enables inheritable static factories and active-record patterns:
User::create()defined inModelcan instantiateUservianew static().parent::still refers to the direct parent.Code Example
<?php class Model { public static function table(): string { return static::TABLE; } } class User extends Model { const TABLE = 'users'; } echo User::table(); // usersKey Points
self::= defining class (compile time)static::= called class (runtime)- Use
new static()for inheritable factories
-
Most asked Explain the SOLID principles with PHP examples.
Summary
SOLID stands for Single responsibility, Open/closed, Liskov substitution, Interface segregation and Dependency inversion, five guidelines for maintainable OO design.
Longer Answer
- S: a class has one reason to change (an
InvoiceMailerdoesn't calculate totals). - O: extend behavior via new classes, e.g. add a
StripeGatewayimplementingPaymentGateway, without editing callers. - L: subtypes must honour parent contracts. A
ReadOnlyRepositorythat throws onsave()violates it. - I: prefer small interfaces (
Readable,Writable) over one fat one. - D: depend on abstractions; inject
PaymentGatewayrather thannew Stripe()inside the service.
Code Example
<?php interface PaymentGateway { public function charge(Money $amount, string $token): string; } final class CheckoutService { public function __construct(private PaymentGateway $gateway) {} // DIP public function pay(Order $order, string $token): string { return $this->gateway->charge($order->total(), $token); } }Key Points
- Small focused classes
- Polymorphism over
switchon types - Honor parent contracts
- Small interfaces
- Constructor injection of abstractions
- S: a class has one reason to change (an
-
Most asked What is dependency injection and how does a DI container help?
Summary
Dependency injection means a class receives its collaborators from outside (usually via the constructor) instead of creating them. A container (PSR-11) builds and wires those object graphs automatically.
Longer Answer
DI makes classes testable (inject fakes) and decoupled. A container such as Symfony DI, Laravel's container or PHP-DI uses reflection/autowiring to resolve constructor type hints, manages lifetimes (shared vs new instance), and binds interfaces to implementations.
Avoid the service locator anti-pattern (pulling
$container->get()inside business classes): it hides dependencies.Key Points
- Constructor injection is preferred
- Container autowires via reflection
- PSR-11 standardizes
get()/has() - Service locator usage hides dependencies
-
Most asked What does `declare(strict_types=1)` do?
Summary
It makes scalar type declarations strict for calls made from that file, so passing
'5'to anintparameter throwsTypeErrorinstead of being coerced.Longer Answer
It is per-file and applies to the caller file: calls originating in a strict file are checked strictly, even into non-strict files. Return types are checked based on the file where the function is defined. The only allowed implicit conversion is int → float.
Key Points
- Per-file, must be the first statement
- Affects calls made from that file
- Only int → float widening is allowed
- Recommended for all new code
-
Most asked What is the N+1 query problem and how do you fix it in PHP ORMs?
Summary
N+1 happens when you load N records and then lazily query a relation for each one, producing N extra queries. Fix it with eager loading (
with()in Eloquent,JOIN FETCH/addSelectin Doctrine) or batched queries.Longer Answer
Example: listing 50 posts and calling
$post->author->nametriggers 51 queries. In Laravel usePost::with('author')->get(); enableModel::preventLazyLoading()in development to catch it. In Doctrine, use DQL withJOIN ... SELECTor batch fetching. Tools: Laravel Debugbar, Symfony Profiler, query logs.Key Points
- Eager load relations you will access
- Detect with profiler or
preventLazyLoading() - Select only needed columns
- Paginate large lists
- What are intersection types (PHP 8.1) and when are they useful?
- What are attributes in PHP 8 and how are they read?
- What is the `never` return type?
- What is first-class callable syntax in PHP 8.1?
- What are `str_contains`, `str_starts_with` and `str_ends_with`?
- What is the `Stringable` interface?
- What did PHP 8.3 add for class constants and overriding?
- What are property hooks in PHP 8.4?
- Why were dynamic properties deprecated in PHP 8.2 and how do you handle it?
- What is the Random extension added in PHP 8.2?
- Explain covariance and contravariance in PHP method signatures.
- Which magic methods matter most in modern PHP?
- What is the difference between `composer install` and `composer update`, and why commit `composer.lock`?
- Which PSRs should a PHP developer know?
- How does PSR-15 middleware work?
- How do you handle file uploads securely in PHP?
- Why is `unserialize()` on user input dangerous?
- How do generators help with memory usage?
- How does PHP handle copying arrays and objects (copy-on-write)?
- Describe PHP's exception hierarchy and good error-handling practices.
- Why prefer `DateTimeImmutable` over `DateTime`?
- Which SPL data structures and interfaces are useful in practice?
- How do you tune PHP-FPM process management?
- How do PHPStan/Psalm and Rector improve a PHP codebase?
- How do you write unit tests in PHP with PHPUnit or Pest?
- How do you use transactions safely with PDO?
- How would you add caching to a PHP application?
- Explain the Repository and Service layer patterns in PHP apps.
- What are Value Objects and DTOs, and how do they differ?
- How do you build a robust JSON API endpoint in plain PHP 8?
- How do you run background jobs in PHP?
- How would you diagnose a slow PHP endpoint?
- How would you plan an upgrade from PHP 7.4 to PHP 8.3+?
- Compare Laravel and Symfony from an architecture standpoint.
- Which php.ini and HTTP settings harden a production PHP app?
- Explain Symfony's HttpKernel request lifecycle and its kernel events.
- What are Disjunctive Normal Form (DNF) types in PHP 8.2?
- What is the `mixed` type and when should you avoid it?
- What does the `static` return type mean?
- What are Fibers in PHP 8.1 and how do they relate to async PHP?
- What is a WeakMap and when would you use it?
- What is the `throw` expression in PHP 8?
- What does `$object::class` do in PHP 8?
- What does PHP 8.1's `new` in initializers allow?
- How did array unpacking change in PHP 8.1?
- What does `array_is_list()` check?
- What is asymmetric visibility in PHP 8.4?
- What else changed in PHP 8.4 besides property hooks?
- What is the pipe operator in PHP 8.5?
- What is the `#[\SensitiveParameter]` attribute for?
- How do you resolve method conflicts between two traits?
- Explain closures, `use`, arrow functions and `Closure::bind`.
- How does PHP's garbage collection work?
- How do you handle Unicode strings correctly in PHP?
- What is OPcache preloading (PHP 7.4+)?
- Compare PHP-FPM with long-running runtimes like Swoole, RoadRunner and FrankenPHP.
- How do you handle PHP sessions across multiple servers?
- How would you implement the Strategy pattern in modern PHP?
- When are anonymous classes useful?
- How do namespaces and `use` imports work in PHP?
- How do `ArrayAccess`, `IteratorAggregate` and `Countable` help build collection classes?
- How do you make HTTP requests from PHP reliably?
- How do you implement logging and observability in PHP?
- How do you containerize a PHP 8 application for production?
- What are PHP superglobals and why shouldn't business code use them directly?
- What are common pitfalls with `preg_*` functions?
- Why are static properties and singletons discouraged?
- How do you write robust PHP CLI scripts?
- Describe how PHP executes a script internally.
- Which array functions should every PHP developer know well?
- How do you control how objects are serialized to JSON?
- What options exist for concurrency or parallelism in PHP?
- How should a PHP app send email reliably?
- Why does running a supported PHP version matter?
- How would you structure a large PHP application using hexagonal architecture?
- How would you implement rate limiting for a PHP API?
- What is the difference between `include`, `require`, `include_once` and `require_once`?