PHP-specific interview questions covering modern language features, frameworks, testing, and best practices.
Show a before/after: "Previously I used string constants for order status. Now I use a backed enum, which gives me type safety, autocomplete, and easy database serialisation."
Good answers explain that typed properties (PHP 7.4+) eliminate the need for manual type checks in setters and make code self-documenting. Enums (PHP 8.1) replace magic strings and class constants with proper type-safe values. Strong candidates give concrete examples: enums for status fields, typed properties in DTOs, and discuss how both improve IDE support and static analysis.
Tests awareness of modern PHP. Candidates stuck on PHP 5.x patterns are a concern. Look for practical understanding, not just feature awareness.
Mention the optimised autoloader for production: "In deployment, we run composer dump-autoload --classmap-authoritative to eliminate filesystem checks."
PSR-4 maps namespace prefixes to directory paths and loads classes on demand. Classmap scans directories and builds a static map of every class. PSR-4 is standard for application code. Classmap is useful for legacy code that does not follow PSR-4 naming. In production, running composer dump-autoload --optimize generates a classmap from PSR-4 mappings for faster loading.
Fundamental PHP ecosystem knowledge. Developers who cannot explain autoloading likely have gaps in understanding dependency management and project structure.
Show framework maturity: "Symfony compiles the container at build time so there is zero runtime resolution cost. Laravel resolves at runtime which is simpler but slightly slower."
Symfony uses a compiled container with explicit service definitions (YAML/XML/PHP attributes), autowiring by type-hint, and auto-configuration. Laravel uses a runtime container with simpler binding syntax, automatic resolution, and service providers. Strong answers discuss trade-offs: Symfony is more explicit and performant for large apps; Laravel is more convenient for rapid development. Best candidates have used both and can articulate when each approach shines.
Tests depth beyond surface-level framework usage. Candidates who only know one framework should still be able to discuss DI principles clearly. Red flag: confusion between service container and service locator pattern.
Lead with measurement: "I profiled with Blackfire, found 60% of request time was N+1 queries, added eager loading, and reduced response time from 800ms to 120ms."
Strong answers mention profiling tools (Xdebug, Blackfire, Tideways) before optimising. Common optimisations: OPcache tuning, database query optimisation (N+1 queries, missing indexes), caching layers (Redis/Memcached), reducing autoloader overhead, preloading (PHP 7.4+), and JIT (PHP 8.0+). Best candidates describe a specific scenario with measurable before/after improvements.
Senior-level question. Developers who optimise without profiling are guessing. Those who can show a systematic approach to performance work are valuable. Ask what tools they use and whether they have production-level profiling experience.
Show a clear philosophy: "I use exceptions for truly unexpected failures and result objects for expected business-logic outcomes like validation. This keeps the happy path clean."
Good answers distinguish between exceptional situations (database down, invalid state) that warrant exceptions and expected outcomes (validation failure, not found) that may use result objects or specific return types. Strong candidates discuss custom exception hierarchies, global exception handlers, error logging, and how frameworks handle this. They mention the difference between Error and Exception in PHP 7+ and the Throwable interface.
Tests code design maturity. Candidates who use exceptions for flow control or silently swallow errors are problematic. Look for a thoughtful, consistent approach.
Be specific: "I unit test service classes with mocked repositories, integration test repositories against a real test database, and use data providers for edge cases."
Strong answers describe a testing pyramid: many unit tests for business logic, fewer integration tests for database/API interactions, and minimal end-to-end tests. Candidates should mention mocking dependencies, test databases, data providers, and code coverage as a guide (not a target). Best answers discuss testing strategies for specific patterns like repositories, services, and controllers.
Tests professional development practices. Developers without testing experience or who only write trivial tests are a risk for code quality. Ask about a bug they caught through testing.
Frame it as a trade-off: "Eloquent is faster to develop with for simple CRUD. Doctrine is better when domain logic is complex and I need entities that are independent of the database."
Active Record (Eloquent): model classes map directly to tables, simpler for CRUD, but business logic can leak into models. Data Mapper (Doctrine): entities are plain objects, the mapper handles persistence separately, better separation of concerns but more setup. Strong candidates discuss testability, complex domain logic, and when the overhead of Data Mapper is justified.
Senior question testing ORM understanding beyond basic usage. Candidates who cannot articulate trade-offs likely have not worked on complex enough projects. Look for experience with at least one pattern in depth.
Pick 2-3 features you actually use and show the improvement: "Constructor promotion cut our DTO boilerplate by 60%. Readonly properties eliminated an entire class of setter-related bugs."
Key PHP 8.x features: named arguments, match expression, constructor promotion, union/intersection types, readonly properties, enums, fibers, first-class callables. Strong candidates describe a real refactoring: reducing boilerplate with constructor promotion, replacing switch statements with match, using readonly properties for value objects, or using enums for state machines.
Tests whether candidates keep up with the language. Those still writing PHP 7.x style code are not leveraging modern tooling. Ask for a specific file or class they refactored.
Show layered thinking: "I use parameterised queries, encode all output, set CSP headers, run composer audit in CI, and use Argon2id for password hashing."
Key concerns: SQL injection (parameterised queries/ORM), XSS (output encoding, CSP headers), CSRF (tokens), session security (httponly/secure flags, regeneration), file upload validation, password hashing (bcrypt/argon2), and dependency vulnerabilities (composer audit). Strong candidates mention security headers, input validation vs output encoding, and defence in depth.
Critical for any PHP developer. Those who only mention SQL injection are thinking too narrowly. Look for awareness of the full attack surface including session handling and dependency security.
Lead with pragmatism: "I would not rewrite it. I would add tests around the most critical paths, extract business logic into classes, and modernise module by module while shipping features."
Best answers describe a pragmatic, incremental approach: add characterisation tests first, then extract logic from templates, introduce autoloading, wrap global state in injectable services, and migrate one module at a time. Strong candidates discuss the Strangler Fig pattern, setting up CI early, and resisting the urge to rewrite from scratch. They balance technical debt reduction with delivering business value.
Tests real-world maturity. Junior developers often want to rewrite everything. Seniors know that incremental modernisation is safer and more sustainable. Ask what they would do first and why.
Show the distinction: "Fibers let a function pause and resume without callbacks, but they are a primitive — I use them through libraries like Revolt. They do not add parallelism. For true async I/O, I pair Fibers with an event loop."
Fibers are lightweight, cooperatively scheduled execution contexts. A Fiber can suspend itself (Fiber::suspend()) and be resumed by the caller, allowing non-blocking I/O without callbacks or promises. They are a low-level building block — most developers use them indirectly through libraries like Revolt (the event loop powering AMPHP and ReactPHP). ReactPHP uses an event loop with promises and callbacks. Swoole replaces the PHP execution model entirely with coroutines and a built-in async server. Strong candidates discuss: that Fibers do not make PHP multithreaded, the difference between cooperative and preemptive scheduling, and why Fibers matter for framework authors more than application developers.
Advanced PHP question. Candidates who confuse Fibers with threads or think they make PHP multithreaded have a fundamental misunderstanding. Those who understand cooperative scheduling and the library ecosystem built on Fibers demonstrate deep language knowledge.
Show the adoption path: "I start at PHPStan level 5, generate a baseline for existing errors, and require zero new errors in CI. Then I raise the level one step at a time, fixing baseline errors as part of regular work."
PHPStan and Psalm analyse code without executing it, catching type errors, undefined methods, dead code, and logic bugs. Both have progressive levels — start at a low level and increase as the codebase improves. For legacy code: use baseline files to ignore existing errors and enforce zero new errors. Key features: generics for collection types, custom PHPDoc types, extension points for framework-specific rules, and CI integration to block merges with new errors. Strong candidates discuss: the practical difference between PHPStan and Psalm, how to handle dynamic frameworks (magic methods, facades), configuring return types for Doctrine repositories, and that static analysis catches different bugs than tests — both are needed.
Tests code quality discipline. Candidates who do not use static analysis are missing an entire category of bug prevention. Those who can describe a progressive adoption strategy with baselines demonstrate practical experience in real codebases.
Ground it in reality: "I used Strategy for payment processing — each provider implements a PaymentGateway interface, and the service picks the right one at runtime. I would not use it if we only ever had one provider — that is a premature abstraction."
Common patterns in PHP: Repository (data access abstraction), Strategy (swappable algorithms), Factory (complex object creation), Observer/Event Dispatcher (decoupled notifications), Decorator (adding behaviour to existing objects), and Builder (step-by-step construction). Good examples of solving real problems: Strategy for payment providers (switch between Stripe and PayPal without changing business logic), Repository for testability (mock the repository, not the database). Over-engineering examples: Abstract Factory for an application with only one concrete implementation, or Observer pattern when a simple method call would suffice. Strong candidates show they apply patterns to solve problems, not to demonstrate knowledge.
Tests design maturity. Candidates who cannot name patterns they use in practice lack experience. Those who apply patterns everywhere without considering trade-offs create unnecessarily complex code. The best answer includes a pattern they chose NOT to use.
Show production awareness: "I commit composer.lock, run composer audit in CI, use caret constraints for flexibility, and review composer update diffs before merging — I want to know exactly what changed and why."
Lock files: composer.lock pins exact versions for reproducible builds — always commit it. Version constraints: use caret (^) for semver-compatible updates, tilde (~) for patch-level only, and exact versions when stability is critical. Security: run composer audit in CI to detect known vulnerabilities. Private packages: use Private Packagist, Satis for self-hosted, or Composer repositories with authentication. Strong candidates discuss: the difference between require and require-dev, platform requirements (php version constraints), conflict resolution when dependencies need incompatible versions, Composer scripts for automation, and the importance of running composer update deliberately rather than blindly.
Tests professional dependency management. Candidates who do not commit composer.lock or who run composer update in production will cause reproducibility issues. Those who audit dependencies and understand version constraints manage supply chain risk.
Cover the security basics: "Regenerate the session ID on login, set httponly and secure flags, use SameSite=Lax, and store sessions in Redis for multi-server deployments. Never trust the session ID — it is a bearer token."
PHP sessions: a session ID is sent via cookie (PHPSESSID), the server stores data in files (by default) keyed by that ID. Security considerations: use session_regenerate_id() after login to prevent session fixation, set cookie flags (httponly, secure, samesite=Lax or Strict), configure session.cookie_lifetime and session.gc_maxlifetime appropriately, use a custom session handler (Redis, database) for multi-server setups, and never store sensitive data in session without encryption. Modern frameworks abstract this: Symfony uses its Session component, Laravel uses its session driver system. Strong candidates discuss: the session garbage collection mechanism, why file-based sessions break with load balancers, session locking and its performance impact, and token-based alternatives (JWT) for stateless APIs.
Tests understanding of PHP internals and web security. Candidates who do not know about session fixation or who use file sessions behind a load balancer will create security and reliability problems. Those who understand session handling build secure auth systems.
Show a consistent approach: "Every endpoint validates input, returns a consistent JSON envelope with data and meta keys, uses proper HTTP status codes, and returns structured error objects with a code and message. I generate OpenAPI docs from code annotations."
Routing: framework routers (Symfony, Laravel, Slim) map HTTP methods and paths to controllers. Validation: validate input at the boundary — use form requests (Laravel), Symfony validators, or dedicated validation libraries. Response formatting: return consistent JSON structures with proper HTTP status codes, use serialisation libraries for transforming entities to API resources, and implement pagination for collections. Versioning: URL prefix (/api/v1) is simplest, header-based is cleaner but harder to test. Error handling: return structured error responses with machine-readable codes and human-readable messages, never leak stack traces in production. Strong candidates discuss: content negotiation, HATEOAS links, rate limiting, OpenAPI documentation generation, and the trade-off between REST and GraphQL.
Tests API architecture skills. Candidates who return inconsistent responses or leak internal errors build frustrating APIs. Those who discuss validation, consistent error formats, and documentation build APIs that other developers want to use.
Layer the caches: "Varnish or Nginx cache handles repeat requests without touching PHP. OPcache makes PHP execution instant. Redis caches database queries and API calls. Each layer has a different invalidation strategy."
Opcode caching (OPcache): caches compiled PHP bytecode, eliminating the parse-compile step on every request — always enable in production. Preloading (PHP 7.4+) goes further by loading classes into memory at startup. Application caching: use PSR-6/PSR-16 cache libraries with backends (Redis, Memcached, APCu) to cache expensive computations, database query results, and API responses. HTTP caching: Cache-Control headers, ETags, and reverse proxies (Varnish, Nginx FastCGI cache) to serve responses without hitting PHP at all. Strong candidates discuss: cache invalidation strategies (TTL, event-based, tag-based), when to use APCu (single-server, fast) versus Redis (multi-server, persistent), and the hierarchy — HTTP cache prevents the request from reaching PHP, OPcache makes PHP execution faster, application cache avoids slow operations.
Tests performance architecture thinking. Candidates who only know one caching layer miss significant optimisation opportunities. Those who understand the cache hierarchy and can discuss invalidation strategies for each layer build high-performance applications.
Show production readiness: "I dispatch jobs to a Redis-backed queue. Workers run under supervisord with automatic restart. Failed jobs go to a failure transport for inspection. Every job is idempotent because at-least-once delivery means it might run twice."
PHP is request-bound by default — background processing requires a queue system. Options: Symfony Messenger, Laravel Queues, or standalone libraries with Redis, RabbitMQ, or database backends. Workers are long-running PHP processes (php bin/console messenger:consume) managed by supervisord or systemd. Key considerations: job serialisation, retry strategies with exponential backoff, dead letter queues for failed jobs, idempotency (jobs may be delivered more than once), and memory management (workers should restart periodically to avoid leaks). Strong candidates discuss: choosing between sync and async transports, monitoring queue depth and processing time, graceful shutdown on deploy, and the specific failure modes of each transport (Redis vs AMQP vs database).
Senior question. Candidates who do heavy work synchronously in HTTP requests build slow, unreliable applications. Those who understand queues, workers, retry strategies, and idempotency design robust async workflows.
Show judgement: "I introduce an interface when I have a concrete reason — multiple implementations, testability, or a published API contract. A single class with no tests and no swap need does not need an interface."
Introduce an interface when: there are or will be multiple implementations (payment gateways, notification channels), you need to mock a dependency for testing, or you want to define a contract that a consumer depends on without coupling to a specific implementation. Do not introduce an interface when: there is only one implementation and no testing benefit, or when it adds indirection without value. SOLID relevance: Interface Segregation (small, focused interfaces), Dependency Inversion (depend on abstractions), and Liskov Substitution (implementations must be interchangeable). Strong candidates discuss: the difference between interfaces and abstract classes in PHP 8, when abstract classes with shared logic are better than interfaces, and that premature abstraction is as harmful as no abstraction.
Tests design thinking. Candidates who interface everything add needless complexity. Those who never use interfaces write tightly coupled code. The best answers show restraint backed by clear reasoning about when abstraction pays for itself.
Prioritise the practical ones: "PSR-4 for autoloading is non-negotiable. PSR-12 style enforced by PHP CS Fixer keeps code consistent. PSR-3 logging and PSR-6 caching let me swap implementations without changing business logic."
Key PSRs: PSR-4 (autoloading), PSR-12 (coding style, successor to PSR-2), PSR-7 (HTTP message interfaces), PSR-11 (container interface), PSR-6/PSR-16 (caching), PSR-3 (logging via Monolog), PSR-15 (HTTP middleware), and PSR-18 (HTTP client). PSR-4 and PSR-12 matter most for daily development — autoloading makes modern PHP work, and consistent style reduces code review friction. PSR-7 and PSR-15 matter for framework interoperability — middleware written against PSR-15 works in any compatible framework. Strong candidates explain: why PSR-7 objects are immutable, how PSR-11 enables framework-agnostic library code, and that coding standards enforcement should be automated (PHP CS Fixer, PHP_CodeSniffer) not manual.
Tests PHP ecosystem knowledge. Candidates who cannot name any PSRs may not be engaged with the broader PHP community. Those who understand which standards enable interoperability and automate style enforcement demonstrate professional practice.
Show the full cycle: "Deploy to a new directory, run composer install and migrations, swap the symlink, then reload PHP-FPM to clear OPcache. If something breaks, swap the symlink back to the previous release."
Zero-downtime deployment: deploy to a new release directory, run builds and migrations, then atomically swap the symlink (tools: Deployer, Envoyer, Capistrano). OPcache: must be reset after deploy to pick up new code — use opcache_reset() via a web endpoint or PHP-FPM reload. Rollback: symlink the previous release directory. Shared directories for uploads, logs, and sessions persist across releases. Strong candidates discuss: the order of operations (migrate before or after symlink swap depending on backwards compatibility), asset compilation, environment-specific configuration, health check endpoints for load balancers, and the risk of database migration rollbacks. They mention that Composer install must run with --no-dev --optimize-autoloader in production.
Senior operations question. Candidates who deploy by overwriting files in place will cause downtime and partial deployments. Those who understand atomic symlink deploys, OPcache management, and rollback strategies deploy reliably.
Show the memory benefit: "Reading a 2GB CSV with file() loads everything into memory. A generator that yields one line at a time uses a few kilobytes regardless of file size. For anything large, generators are the right tool."
Generators use the yield keyword to produce values one at a time without building the entire result set in memory. A function returning 1 million rows as an array allocates memory for all of them. A generator yields one row at a time, using constant memory regardless of dataset size. Use generators for: iterating over large files line by line, streaming database results, and any pipeline where you process items sequentially. Generators implement the Iterator interface and work with foreach. Strong candidates discuss: yield from for delegating to sub-generators, send() for two-way communication, the return value of a generator (getReturn()), and when generators are overkill (small datasets where an array is simpler).
Tests understanding of PHP memory management. Candidates who always build arrays will hit memory limits on large datasets. Those who reach for generators when processing large collections write scalable code.
Show the evolution: "PHP 5.3 closures needed use() for every variable. Arrow functions capture automatically. First-class callables in 8.1 let me pass strlen(...) directly instead of wrapping it. Each step reduced boilerplate."
Closures (anonymous functions) arrived in PHP 5.3 with use() for variable binding. Short closures (arrow functions, fn() =>) came in PHP 7.4 with automatic variable capture and single-expression bodies. First-class callable syntax ($object->method(...)) arrived in PHP 8.1, replacing Closure::fromCallable(). Use closures for: callbacks, array_map/array_filter, event handlers, and small scoped logic. Prefer class-based approaches when: the logic is complex, needs testing in isolation, or is reused across multiple contexts. Strong candidates discuss: Closure::bind() and Closure::call() for rebinding $this, the performance characteristics of closures versus invokable classes, and that arrow functions solve the common annoyance of use($var) for simple callbacks.
Tests language fluency. Candidates stuck on PHP 5.x closure syntax are not using modern features. Those who understand the full evolution and can choose between closures and classes based on complexity demonstrate practical language mastery.
Show your priorities: "Security first — injection, XSS, missing auth checks. Then correctness — error handling, edge cases, test coverage. Then maintainability — naming, complexity, coupling. I automate style and type checking so reviews focus on design."
Systematic review: start with the big picture (does the approach make sense?), then check details. Common issues to flag: SQL injection or XSS vulnerabilities, missing input validation at boundaries, N+1 query patterns, improper error handling (empty catches, swallowed exceptions), hardcoded configuration, untested edge cases, naming that obscures intent, unnecessary complexity, and missing type declarations. Process: read the PR description first, check tests exist and cover the change, review the diff file by file, and comment constructively with alternatives. Strong candidates discuss: automated checks (CI running PHPStan, tests, style) that free up human reviewers for logic and design questions, the balance between thoroughness and turnaround time, and how to give feedback that teaches rather than just corrects.
Tests professional collaboration skills. Candidates who only check for style issues are not catching real problems. Those with a systematic approach that prioritises security and correctness produce higher-quality code through reviews.
Show the modern approach: "A readonly class with promoted constructor parameters gives me an immutable DTO in three lines. For value objects, I add validation in the constructor and an equals() method. PHP 8.2 made this pattern practically free."
Value objects represent concepts defined by their values rather than identity — two Money objects with the same amount and currency are equal regardless of identity. DTOs (Data Transfer Objects) carry data between layers without behaviour. PHP 8.2 readonly classes make both trivial to implement: all properties are readonly by default, immutability is enforced by the language, and constructor promotion eliminates boilerplate. Strong candidates discuss: the difference between value objects (have equality logic and business rules) and DTOs (pure data carriers), when to use readonly properties versus readonly classes, implementing equals() methods, and using value objects to replace primitive obsession (passing $email as string vs Email value object with validation).
Tests modern PHP design. Candidates who pass arrays of data between layers lose type safety and validation. Those who use value objects and DTOs with readonly classes write self-documenting, safe code with minimal boilerplate.