Skip to content

Cookies on Jobverse

We use cookies that are needed to sign you in and keep the site working. With your permission we’d also use an analytics cookie to count job views, and Google advertising cookies to show ads. The site works the same whatever you choose. Read the Cookie Policy.

Jobverse
Sign in

PHP Backend Developer Top 100 Interview Questions

100 PHP backend developer interview questions with structured answers, comparison tables, flow diagrams, worked calculations and runnable code. Covers modern typed PHP, the request lifecycle, HTTP and APIs, security, PDO and databases, caching, queues, testing, performance, deployment and system design, from the basics to production incidents.

100 questions

Questions 21-40 of 100

  1. Q21.What do the bytecode cache and the just-in-time compiler do for PHP performance?

    PHP compiles source files into opcodes before running them. The bytecode cache, OPcache, stores compiled opcodes in shared memory, so later requests skip parsing and compiling. The just-in-time compiler, built on top of it, can turn hot opcodes into machine code.

    Bytecode cache in production:

    1. Always enable it; it typically gives a large speedup for web applications.
    2. Size its memory and the maximum number of cached files for your codebase, with room to spare.
    3. Disable timestamp checks in production for speed, and reset the cache on deployment.
    4. Preloading can load framework and application classes into shared memory at server start, so they are available to every request without loading.

    Just-in-time compilation:

    • Helps most for CPU-heavy code, such as calculations, image processing or long-running workers.
    • Gives modest gains for typical web requests, which spend most time waiting on databases and networks.
    • Should be benchmarked with your own workload before enabling it widely.

    What neither fixes: slow queries, N+1 database access and blocking calls to external services, which usually dominate request time.

  2. Q22.How do you work correctly with strings and multibyte text in PHP?

    PHP strings are byte arrays. Standard functions such as strlen, substr and strtoupper work on bytes, which breaks text in languages that use multibyte UTF-8 characters. Use the multibyte functions, prefixed mb_, for anything involving user-visible text.

    Rules:

    1. Use mb_strlen, mb_substr, mb_strtolower and similar functions for characters, with UTF-8 as the internal encoding.
    2. Never cut strings with substr for display; it can split a character and produce invalid UTF-8.
    3. Validate encoding of input with mb_check_encoding, rejecting invalid byte sequences.
    4. Escape on output with htmlspecialchars($text, ENT_QUOTES, "UTF-8").
    5. Normalize text for comparisons and search when users can type the same character in different forms.
    6. Store UTF-8 consistently in the database, including connection settings.

    Performance: byte functions are faster and fine for ASCII data such as identifiers, hexadecimal tokens and protocol fields; use them deliberately there.

    Example

    Byte-based functions miscount and cut characters; multibyte functions handle them correctly.

    <?php
    declare(strict_types=1);
    
    $name = "Søren Ñandú";
    echo strlen($name), " ", mb_strlen($name), "\n";
    echo mb_substr($name, 0, 7), "\n";
    var_dump(mb_check_encoding(substr($name, 0, 2)));
    echo mb_strtoupper($name), "\n";
    // Output:
    // 14 11
    // Søren Ñ
    // bool(false)
    // SØREN ÑANDÚ
  3. Q23.How do you handle dates, times and time zones in PHP?

    Use DateTimeImmutable, store and exchange times in UTC with a standard format and convert to the user's time zone only for display. The mutable DateTime class changes in place, which causes bugs when an object is shared.

    Good practices:

    1. Set the default time zone explicitly in configuration, and prefer UTC on servers.
    2. Immutable objects: $tomorrow = $today->modify("+1 day") returns a new object, leaving $today unchanged.
    3. Standard formats: DATE_ATOM or DateTimeInterface::RFC3339_EXTENDED in APIs.
    4. Parse strictly: DateTimeImmutable::createFromFormat with an exact format, and check for errors, rather than accepting any string.
    5. Inject a clock into code that depends on "now", so tests can control time.
    6. Calendar arithmetic: be aware of daylight saving transitions and month ends; adding one month to January 31 can produce March dates.

    Storage: store timestamps as UTC in the database, and store the user's time zone separately if you need local times later.

    Example

    A UTC timestamp is shown in another zone, the immutable original is unchanged and month arithmetic overflows.

    <?php
    declare(strict_types=1);
    
    $utc = new DateTimeImmutable("2026-03-01 09:30:00", new DateTimeZone("UTC"));
    $local = $utc->setTimezone(new DateTimeZone("Asia/Kolkata"));
    echo $local->format(DATE_ATOM), "\n";                    // 2026-03-01T15:00:00+05:30
    echo $utc->format("H:i"), "\n";                          // 09:30
    
    $jan31 = new DateTimeImmutable("2026-01-31");
    echo $jan31->modify("+1 month")->format("Y-m-d"), "\n";  // 2026-03-03
    echo $jan31->modify("last day of next month")->format("Y-m-d"), "\n"; // 2026-02-28
  4. Q24.How do attributes work in PHP?

    Attributes add structured metadata to classes, methods, properties, parameters and constants, written as #[Name(arguments)]. Code reads them through reflection, which lets libraries configure behavior declaratively, such as routes, validation rules or serialization names, without parsing comments.

    How they work:

    1. Define an attribute as a class marked with #[Attribute], optionally restricting where it can be used.
    2. Apply it: #[Route("/orders", method: "GET")].
    3. Read it: $reflection->getAttributes(Route::class) returns attribute descriptions, and newInstance() creates the attribute object.

    Built-in attributes include #[Override], which makes PHP check that a method really overrides a parent method, #[SensitiveParameter], which hides values from stack traces, and #[Deprecated].

    Good practices:

    • Cache reflection results, since reading attributes on every request is wasteful.
    • Keep attributes declarative; the logic belongs in the code that reads them.
    • Use #[SensitiveParameter] on passwords and tokens, so they never appear in logs.

    Example

    A tiny router reads route attributes from controller methods through reflection.

    <?php
    declare(strict_types=1);
    
    #[Attribute(Attribute::TARGET_METHOD)]
    final class Route
    {
        public function __construct(public string $path, public string $method = "GET") {}
    }
    
    final class OrderController
    {
        #[Route("/orders")]
        public function list(): string { return "list"; }
    
        #[Route("/orders", method: "POST")]
        public function create(): string { return "create"; }
    
        public function helper(): void {}
    }
    
    foreach ((new ReflectionClass(OrderController::class))->getMethods() as $method) {
        foreach ($method->getAttributes(Route::class) as $attribute) {
            $route = $attribute->newInstance();
            echo "{$route->method} {$route->path} -> {$method->getName()}\n";
        }
    }
    // Output:
    // GET /orders -> list
    // POST /orders -> create
  5. Q25.What does idiomatic modern PHP code look like?

    Modern PHP looks very different from older scripts that mixed HTML, SQL and logic in one file. It is typed, object-oriented where useful, organized into namespaces and small classes and tested.

    Core habits:

    1. declare(strict_types=1); in every file, and types on all parameters, returns and properties.
    2. Small, focused classes with constructor injection, and interfaces at boundaries.
    3. Immutable value objects with readonly properties for data such as money and identifiers.
    4. Enums instead of magic strings and constants.
    5. Exceptions for failures, handled centrally at the edge of the application.
    6. Prepared statements for every query, and escaping for every output.
    7. A standard code style enforced automatically, and static analysis at a strict level in continuous integration.
    8. No global state: no global keyword and minimal static properties.
    9. Generators and streaming for large data.

    What to avoid: suppressing errors with @, building SQL with string concatenation, extract and dynamic variable names, and deeply nested arrays used as untyped data structures.

    Interview tip: contrasting a short snippet of old-style code with its modern, typed equivalent is a quick way to show you know how far the language has come.

  6. Q26.How does request data reach a PHP application?

    For each request, PHP populates superglobal arrays from the incoming HTTP request before your code runs. $_GET holds query string values, $_POST holds form fields for form-encoded bodies, $_COOKIE holds cookies, $_FILES describes uploaded files and $_SERVER holds headers and server information, with headers prefixed by HTTP_.

    Raw bodies: JSON and other non-form bodies are not parsed automatically; read them with file_get_contents("php://input") and decode them yourself.

    Good practices:

    1. Treat every value as untrusted text: all superglobal values are strings or arrays of strings, and keys can be missing.
    2. Wrap superglobals in a request object at the entry point, so the rest of the code receives typed, validated data and can be tested without globals.
    3. Validate and convert at the boundary, for example turning "20" into an integer page size within allowed limits.
    4. Never trust client-supplied headers such as the client IP address unless they come from your own proxy, and configure which proxies are trusted.
    5. Limit sizes with server and PHP settings for post size, upload size and input variable counts, so huge requests are rejected early.

    Avoid reading $_REQUEST, which mixes sources and lets cookies or query strings override form data unexpectedly.

  7. Q27.How do you build a front controller and router in PHP?

    A front controller is a single entry script, such as public/index.php, that receives every request. It creates the request object, asks a router to match the method and path to a handler and sends the handler's response. Keeping one entry point makes it easy to apply security, logging and error handling consistently.

    How a router works:

    1. Routes are registered with a method, a path pattern and a handler, such as GET /orders/{id}.
    2. Patterns are compiled to regular expressions, with named groups for parameters.
    3. Matching returns the handler and the extracted parameters, or a 404 when nothing matches, or a 405 when the path matches but the method does not.

    Production concerns:

    • Compile and cache routes, so matching stays fast with hundreds of routes.
    • Validate parameters after matching, such as checking that an ID is numeric.
    • Keep the public directory minimal: only the front controller and static assets should be reachable; source code and configuration live outside it.
    • Order matters for overlapping patterns; specific routes should come before general ones.

    Example

    A tiny router compiles patterns into regular expressions and distinguishes 404 from 405.

    <?php
    declare(strict_types=1);
    
    final class Router
    {
        private array $routes = [];
    
        public function add(string $method, string $pattern, callable $handler): void
        {
            $regex = '#^' . preg_replace('#\{(\w+)\}#', '(?P<$1>[^/]+)', $pattern) . '$#';
            $this->routes[] = [$method, $regex, $handler];
        }
    
        public function dispatch(string $method, string $path): array
        {
            $methodMismatch = false;
            foreach ($this->routes as [$routeMethod, $regex, $handler]) {
                if (!preg_match($regex, $path, $matches)) {
                    continue;
                }
                if ($routeMethod !== $method) {
                    $methodMismatch = true;
                    continue;
                }
                $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
                return [200, $handler(...$params)];
            }
            return $methodMismatch ? [405, 'method not allowed'] : [404, 'not found'];
        }
    }
    
    $router = new Router();
    $router->add('GET', '/orders/{id}', fn (string $id): string => "order $id");
    $router->add('GET', '/users/{user}/orders/{id}', fn (string $user, string $id): string => "order $id of $user");
    
    foreach ([['GET', '/orders/42'], ['GET', '/users/u7/orders/9'], ['DELETE', '/orders/42'], ['GET', '/missing']] as [$m, $p]) {
        [$status, $body] = $router->dispatch($m, $p);
        echo "$status $body\n";
    }
    // Output:
    // 200 order 42
    // 200 order 9 of u7
    // 405 method not allowed
    // 404 not found
  8. Q28.How do middleware pipelines work in PHP applications?

    Middleware wraps the request handler in layers. Each layer receives the request and the next handler, can act before passing the request on, can stop the chain by returning its own response and can modify the response on the way back. The first layer added wraps everything else.

    flowchart LR
      A["Request"] --> B["Error handling"]
      B --> C["Logging"]
      C --> D["Authentication"]
      D --> E["Handler"]
      E --> D
      D --> C
      C --> B
      B --> F["Response"]

    Typical layers:

    • Error handling that converts exceptions into proper error responses.
    • Logging with timing and request IDs.
    • Authentication and authorization.
    • Body parsing, CORS headers and rate limiting.

    Design rules:

    1. Order matters: error handling outermost, authentication before authorization, handlers last.
    2. Immutable request and response objects make middleware safer, since layers return modified copies instead of changing shared state.
    3. One concern per layer, each easy to test with a fake next handler.
    4. Short-circuit cleanly: an authentication failure returns a 401 response without calling the next layer.

    Many PHP frameworks follow the same interface style, so middleware written this way is easy to reuse.

    Example

    A pipeline built with array_reduce: the logger wraps everything, and authentication stops unauthenticated requests.

    <?php
    declare(strict_types=1);
    
    $handler = fn (array $req): array => [200, "hello {$req['user']}"];
    
    $middleware = [
        function (array $req, callable $next): array {
            [$status, $body] = $next($req);
            echo "log: {$req['path']} -> $status\n";
            return [$status, $body];
        },
        function (array $req, callable $next): array {
            if (($req['token'] ?? '') !== 'valid') {
                return [401, 'unauthorized'];
            }
            return $next($req + ['user' => 'u7']);
        },
    ];
    
    $app = array_reduce(
        array_reverse($middleware),
        fn (callable $next, callable $layer): callable => fn (array $req): array => $layer($req, $next),
        $handler,
    );
    
    foreach ([['path' => '/me', 'token' => 'valid'], ['path' => '/me']] as $request) {
        [$status, $body] = $app($request);
        echo "$status $body\n";
    }
    // Output:
    // log: /me -> 200
    // 200 hello u7
    // log: /me -> 401
    // 401 unauthorized
  9. Q29.How do you validate and sanitize input in PHP?

    Validate input against what you expect, convert it to proper types and reject anything else, then escape data for the context where it is used, such as HTML or SQL. Sanitizing by stripping characters is not a substitute for validation and context-specific escaping.

    Tools in the standard library:

    • filter_var with validation filters: integers with ranges, floats, Booleans, email addresses, URLs and IP addresses. It returns the converted value or false.
    • ctype_digit and similar functions for character-class checks.
    • Regular expressions for formats such as order references, anchored with ^ and $.
    • Enums and tryFrom for fixed sets of values.

    Validation practices:

    1. Allowlist, do not blocklist: define what is valid rather than trying to list everything dangerous.
    2. Check lengths and ranges, so huge or extreme values are rejected.
    3. Collect all errors and return them together with status 400 or 422.
    4. Validate once at the boundary, then pass typed value objects to the rest of the code.

    Escaping is separate: use prepared statements for SQL, htmlspecialchars for HTML and the right encoder for every other output context.

    Example

    Validation filters convert valid input and return false for invalid input instead of guessing.

    <?php
    declare(strict_types=1);
    
    $input = ['email' => ' buyer@example.com ', 'age' => '29', 'quantity' => '5abc', 'website' => 'not a url'];
    
    var_dump(filter_var(trim($input['email']), FILTER_VALIDATE_EMAIL));
    var_dump(filter_var($input['age'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 18, 'max_range' => 120]]));
    var_dump(filter_var($input['quantity'], FILTER_VALIDATE_INT));
    var_dump(filter_var($input['website'], FILTER_VALIDATE_URL));
    // Output:
    // string(17) "buyer@example.com"
    // int(29)
    // bool(false)
    // bool(false)
  10. Q30.How do you return JSON from a PHP API correctly?

    Encode responses with json_encode using JSON_THROW_ON_ERROR, set the Content-Type: application/json header and the correct status code, and decode requests with the same flag, so malformed data raises an exception instead of silently returning null.

    Encoding details:

    1. Empty objects versus arrays: an empty PHP array encodes as []; use new stdClass() or an object type when the contract requires {}.
    2. Unicode and slashes: JSON_UNESCAPED_UNICODE and JSON_UNESCAPED_SLASHES produce more readable output.
    3. Floats: values such as 0.1 + 0.2 may encode with many digits; format money as integers in minor units or as strings.
    4. Objects: implement JsonSerializable to control how a class is encoded.

    Decoding details:

    • json_decode($body, true, 512, JSON_THROW_ON_ERROR) returns arrays, with a depth limit.
    • Large integers beyond the platform's integer range become floats and lose precision unless decoded with JSON_BIGINT_AS_STRING.
    • Validate the decoded structure, since valid JSON can still have the wrong shape.

    Responses: keep one envelope or shape for errors across the API, and never echo warnings or debug output into JSON responses.

    Example

    Empty arrays versus objects, a thrown parse error and large integers decoded safely.

    <?php
    declare(strict_types=1);
    
    echo json_encode(['items' => [], 'meta' => new stdClass(), 'path' => '/orders/7'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES), "\n";
    
    try {
        json_decode('{bad json', true, 512, JSON_THROW_ON_ERROR);
    } catch (JsonException $e) {
        echo "invalid JSON: ", $e->getMessage(), "\n";
    }
    
    $raw = '{"id": 12345678901234567890}';
    var_dump(json_decode($raw, true)['id']);
    echo json_decode($raw, true, 512, JSON_BIGINT_AS_STRING)['id'], "\n";
    // Output:
    // {"items":[],"meta":{},"path":"/orders/7"}
    // invalid JSON: Syntax error
    // float(1.2345678901234567E+19)
    // 12345678901234567890
  11. Q31.How do PHP sessions work, and how do you secure them?

    A session stores data on the server, keyed by a random session ID sent to the browser in a cookie. On each request, PHP reads the cookie, loads the session data, lets the script read and change it and saves it at the end of the request.

    sequenceDiagram
      participant B as Browser
      participant P as PHP worker
      participant S as Session storage
      B->>P: Request with session cookie
      P->>S: Load data for the session ID, and lock it
      P->>P: Run the script, read and write the session
      P->>S: Save data and release the lock
      P-->>B: Response, cookie refreshed

    Securing sessions:

    1. Cookie flags: Secure, HttpOnly and SameSite=Lax or Strict.
    2. Regenerate the ID with session_regenerate_id(true) after login and privilege changes, to prevent session fixation.
    3. Strict mode: reject session IDs the server did not create.
    4. Timeouts: expire idle sessions and set an absolute lifetime.
    5. Destroy on logout, both the server data and the cookie.

    Scaling: file-based sessions do not work across several servers; store sessions in a shared database or cache. Sessions lock by default, so concurrent requests from one user wait for each other; close the session early with session_write_close when the script no longer needs to write.

  12. Q32.Which cookie attributes matter for a PHP application, and why?

    Cookie attributes decide when the browser sends a cookie and what scripts can do with it, which makes them a key defense for session cookies and other sensitive values. Set them explicitly with setcookie options or session configuration.

    SecureHttpOnlySameSitePath and Domain
    Sent only over HTTPSNot readable from page scriptsControls sending on cross-site requestsLimit which paths and hosts receive the cookie
    Prevents leaking the cookie over plain HTTPLimits damage from cross-site scriptingLax blocks most cross-site request forgery; Strict blocks all cross-site sendingNarrow scope reduces exposure to other applications
    Always on in productionAlways on for session cookiesLax is a good default; None requires SecureKeep as narrow as the application allows

    Other practices:

    1. Expiry: session cookies without an expiry end when the browser closes; persistent login cookies need a reasonable maximum age and server-side revocation.
    2. Prefixes: cookie names starting with __Host- must be secure, have no domain and use the root path, which prevents subdomains from overwriting them.
    3. Store little: keep only identifiers in cookies, never sensitive data, and sign any value whose integrity matters.
  13. Q33.How do you hash and verify passwords in PHP?

    Use password_hash to create a hash and password_verify to check a password against it. These functions use slow, salted algorithms designed for passwords, such as Argon2id or bcrypt, include the salt and settings in the stored string and protect against timing attacks.

    Rules:

    1. Never store plain or reversibly encrypted passwords, and never use fast hashes such as SHA-256 alone for passwords.
    2. Choose the algorithm and cost so hashing takes tens of milliseconds on your servers; Argon2id is a strong default when available.
    3. Rehash on login: after a successful verification, call password_needs_rehash, and store a new hash if the algorithm or cost has been upgraded.
    4. Store the full hash string, in a column wide enough for future algorithms.
    5. Rate-limit login attempts and add monitoring, since slow hashing alone does not stop online guessing.

    Mark password parameters with #[SensitiveParameter], so their values never appear in stack traces or logs.

    Example

    The hash verifies the right password, rejects a wrong one and reports that stronger settings require rehashing.

    <?php
    declare(strict_types=1);
    
    $hash = password_hash('long-passphrase-42', PASSWORD_ARGON2ID);
    
    var_dump(str_starts_with($hash, '$argon2id$'));
    var_dump(password_verify('long-passphrase-42', $hash));
    var_dump(password_verify('wrong-guess', $hash));
    var_dump(password_needs_rehash($hash, PASSWORD_ARGON2ID, ['memory_cost' => 131072]));
    // Output:
    // bool(true)
    // bool(true)
    // bool(false)
    // bool(true)
  14. Q34.How do you prevent SQL injection with PDO?

    Use prepared statements with bound parameters for every value that comes from outside the query. The query structure is sent separately from the values, so input can never change the meaning of the SQL.

    PDO settings that help:

    1. Exceptions for errors: PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, so failures are not silently ignored.
    2. Real prepared statements: turn off emulation where the driver supports native preparation, so types are handled by the database.
    3. Typed binding: bind integers as integers, which matters for clauses such as LIMIT.

    Cases that need extra care:

    • Identifiers such as column names for sorting cannot be bound; map user choices to a fixed allowlist of column names.
    • IN lists: generate one placeholder per value.
    • LIKE searches: bind the pattern, and escape % and _ in user input if they should be literal.

    Defense in depth: a database user with only the permissions the application needs, input validation at the boundary and logging that never includes full queries with personal data.

    Example

    Concatenating input lets a crafted value match every row; the prepared statement treats it as a plain string. The connection string comes from the DB_DSN environment variable, here an in-memory database.

    <?php
    declare(strict_types=1);
    
    $db = new PDO(getenv('DB_DSN'), options: [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
    $db->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT, role TEXT)");
    $db->exec("INSERT INTO users (email, role) VALUES ('a@example.com', 'admin'), ('b@example.com', 'user')");
    
    $attack = "x' OR '1'='1";
    
    $unsafe = $db->query("SELECT COUNT(*) FROM users WHERE email = '$attack'")->fetchColumn();
    
    $stmt = $db->prepare('SELECT COUNT(*) FROM users WHERE email = :email');
    $stmt->execute(['email' => $attack]);
    $safe = $stmt->fetchColumn();
    
    echo "concatenated: $unsafe rows, prepared: $safe rows\n"; // concatenated: 2 rows, prepared: 0 rows
  15. Q35.How do you prevent cross-site scripting in PHP applications?

    Escape every piece of untrusted data for the exact context where it is output. Cross-site scripting happens when user-controlled text is inserted into a page and the browser runs it as code.

    Escaping by context:

    1. HTML text and attributes: htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), always with quotes around attribute values.
    2. URLs in links: validate the scheme against an allowlist, such as http and https, so javascript: links are rejected, and encode parameters with rawurlencode.
    3. Data inside scripts: encode with json_encode and the JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS and JSON_HEX_QUOT flags, rather than inserting raw strings.
    4. CSS and other contexts: avoid inserting user data at all where possible.

    Additional defenses:

    • Templates that escape by default, so forgetting to escape is impossible rather than easy.
    • A content security policy header that blocks inline scripts and limits script sources.
    • HttpOnly cookies, so stolen-cookie attacks are harder even if a flaw exists.
    • Sanitize rich text with a dedicated HTML sanitizer that allows only a safe subset of tags, if users may submit formatted content.

    Example

    HTML escaping neutralizes markup, a link with a dangerous scheme is replaced and data for scripts is encoded safely.

    <?php
    declare(strict_types=1);
    
    $comment = '<script>steal()</script> & "quotes"';
    echo htmlspecialchars($comment, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), "\n";
    
    $link = 'javascript:alert(1)';
    $scheme = parse_url($link, PHP_URL_SCHEME);
    echo in_array($scheme, ['http', 'https'], true) ? $link : '#', "\n";
    
    echo json_encode(['name' => '</script><b>x</b>'], JSON_HEX_TAG | JSON_UNESCAPED_SLASHES), "\n";
    // Output:
    // &lt;script&gt;steal()&lt;/script&gt; &amp; &quot;quotes&quot;
    // #
    // {"name":"\u003C/script\u003E\u003Cb\u003Ex\u003C/b\u003E"}
  16. Q36.How do you prevent cross-site request forgery in PHP applications?

    Cross-site request forgery tricks a logged-in user's browser into sending a request to your site from another site, using the user's cookies. Defend state-changing requests by requiring something an attacker's page cannot obtain or send, such as a secret token or a same-site cookie restriction.

    Synchronizer tokenDouble-submit cookieSame-site cookies
    A random token stored in the session and embedded in each formA random token in a cookie, also sent in a header or fieldThe browser does not send the session cookie on cross-site requests
    Server compares the submitted token with the session copyServer checks that both values matchBuilt into the cookie, with no extra code
    Strong and widely usedWorks without server-side storageStrong baseline, but rely on it together with tokens

    Implementation details:

    1. Generate tokens with random_bytes, encoded as hexadecimal or base64.
    2. Compare with hash_equals, to avoid timing leaks.
    3. Protect every state-changing method: POST, PUT, PATCH and DELETE, never with GET requests that change data.
    4. APIs using bearer tokens in headers are not exposed in the same way, since browsers do not attach those headers automatically, but cookie-authenticated APIs are.
  17. Q37.How do you handle file uploads securely in PHP?

    Treat every uploaded file as hostile: check it was uploaded through HTTP, validate size and real content type, store it outside the public directory under a generated name and serve it through code that sets safe headers.

    1. Check the upload result: the error code in $_FILES, and is_uploaded_file, then move it with move_uploaded_file.
    2. Limit size in PHP configuration and in your own validation, rejecting oversized files early.
    3. Detect the real type from the file's content, for example with the file information extension, and allow only expected types; never trust the client's declared type or the extension.
    4. Generate the stored name, such as a random identifier, and keep the original name only as metadata.
    5. Store outside the web root, or in object storage, so uploaded files can never be executed as scripts.
    6. Serve downloads safely: set Content-Type, Content-Disposition: attachment where appropriate and X-Content-Type-Options: nosniff.
    7. Process images by re-encoding them, which strips embedded scripts and metadata, and scan files for malware when risk is high.

    For large files, let clients upload directly to object storage with short-lived signed URLs, so PHP workers are not tied up streaming bytes.

  18. Q38.How do you make outgoing HTTP calls reliably from PHP?

    Use an HTTP client library or extension that supports timeouts, connection reuse and clear error handling, and wrap external services behind your own small client classes. A missing timeout is the most common cause of PHP workers hanging on slow dependencies.

    Rules:

    1. Set connect and total timeouts on every request; a worker stuck for 60 seconds is unavailable to all other users.
    2. Check status codes explicitly, and treat non-success responses as errors with useful context.
    3. Retry only safe operations on transient failures, with backoff and jitter, within a total time budget.
    4. Reuse connections where the runtime allows it, which matters most for long-running workers.
    5. Validate URLs built from user input to prevent server-side request forgery, and block internal address ranges.
    6. Limit response sizes before reading bodies into memory.

    Moving work off the request: for slow or unreliable dependencies, such as sending emails or calling partner APIs, queue the work and process it in a background worker, returning a quick response to the user.

    Testing: inject the client behind an interface, so tests can use a fake that returns chosen responses, including timeouts and errors.

  19. Q39.How do you implement token-based authentication in PHP?

    Issue a token after login, have clients send it in the Authorization header and verify it on every request. A signed token carries claims, such as the user ID and expiry, and an HMAC signature that the server checks with a secret key, so the token cannot be changed without detection.

    Verifying a signed token:

    1. Split it into the payload and the signature.
    2. Recompute the signature with the server's secret key and compare with hash_equals.
    3. Decode the payload and check the expiry, issuer and audience.
    4. Load the user and permissions, and reject the request with 401 if anything fails.

    Practical advice:

    • Prefer well-reviewed token libraries in production, which handle algorithm choice, key rotation and edge cases.
    • Short lifetimes with refresh tokens, because signed tokens cannot easily be revoked before expiry.
    • Opaque tokens stored server-side are simpler to revoke and fine for many applications.
    • Keep secrets out of code, rotate keys with key identifiers in tokens and never put sensitive data in payloads, which are only encoded, not encrypted.

    Example

    A token signed with HMAC verifies, a tampered token fails and an expired token is rejected.

    <?php
    declare(strict_types=1);
    
    function b64url(string $data): string
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }
    
    function sign(array $claims, string $key): string
    {
        $body = b64url(json_encode($claims, JSON_THROW_ON_ERROR));
        return $body . '.' . b64url(hash_hmac('sha256', $body, $key, true));
    }
    
    function verify(string $token, string $key, int $now): ?array
    {
        [$body, $signature] = explode('.', $token, 2) + [1 => ''];
        if (!hash_equals(b64url(hash_hmac('sha256', $body, $key, true)), $signature)) {
            return null;
        }
        $claims = json_decode(base64_decode(strtr($body, '-_', '+/')), true, 512, JSON_THROW_ON_ERROR);
        return $claims['exp'] > $now ? $claims : null;
    }
    
    $key = 'server-side-secret';
    $token = sign(['sub' => 'u-7', 'exp' => 1_000_100], $key);
    
    echo verify($token, $key, 1_000_000)['sub'] ?? 'rejected', "\n";       // u-7
    echo verify($token . 'x', $key, 1_000_000)['sub'] ?? 'rejected', "\n"; // rejected
    echo verify($token, $key, 1_000_200)['sub'] ?? 'rejected', "\n";       // rejected
  20. Q40.How do you return consistent error responses from a PHP API?

    Handle errors in one place, map known failures to the right HTTP status and return them in one JSON shape across the whole API. Clients can then handle errors generically, and no stack trace or internal detail leaks.

    Central handling:

    1. A top-level try/catch in the front controller, or error-handling middleware, catches every Throwable.
    2. Convert PHP warnings and notices to exceptions with set_error_handler, so problems are not printed into responses.
    3. Map exceptions to statuses: validation failures to 422, not found to 404, conflicts to 409, authentication to 401, authorization to 403 and everything unexpected to 500.
    4. Log unexpected errors with the request ID and stack trace, and return only a generic message and the request ID to the client.

    A consistent body shape, such as the open problem details format, includes a type, a title, the status, a detail message and, for validation, a list of field errors.

    Production settings: display_errors off, log_errors on and an error reporting level that includes everything, so issues are logged rather than shown.