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 1-20 of 100

  1. Q1.What does a PHP backend developer own beyond writing request handlers?

    A PHP backend developer owns the whole service: its API design, data access, security, performance and behavior in production. Writing the code that answers a request is only the visible part.

    Typical responsibilities:

    • API and application design: clear routes, validation, consistent errors and versioning.
    • Data access: safe queries with prepared statements, transactions, migrations and caching.
    • Security: password hashing, session and cookie safety, protection against injection, cross-site scripting and request forgery.
    • Performance: bytecode caching, query efficiency, profiling and sizing the process pool.
    • Background work: queues and workers for slow tasks, such as emails and report generation.
    • Testing and quality: unit and integration tests, strict types and static analysis.
    • Operations: configuration, logging, deployments without downtime and dependency upgrades.

    Modern PHP is fast and strongly typed when used well, so a strong developer writes typed, testable code and understands how the runtime executes each request.

  2. Q2.How does a PHP request lifecycle work in a typical production setup?

    In the common setup, a web server receives the HTTP request and passes it to a pool of PHP worker processes managed by the FastCGI process manager. One worker runs the application from the start for that request, sends the response and then discards all request state before taking the next request.

    sequenceDiagram
      participant C as Client
      participant W as Web server
      participant P as Process manager
      participant K as PHP worker
      C->>W: HTTP request
      W->>P: FastCGI request
      P->>K: Assign an idle worker
      K->>K: Load cached bytecode, run the script
      K-->>W: Response
      W-->>C: HTTP response
      K->>K: Free all request memory

    What this "share-nothing" model means:

    1. No state survives between requests in a worker, so memory leaks within a request rarely accumulate, and one bad request cannot corrupt the next.
    2. Bootstrapping happens every request, so frameworks and applications rely on bytecode caching and cached configuration to stay fast.
    3. Shared state lives outside PHP, in databases, caches or session stores.
    4. Concurrency equals worker count: each worker handles one request at a time, so slow requests tie up workers.

    Long-running application servers for PHP also exist; they keep the application in memory between requests, trading simplicity for speed.

  3. Q3.How does PHP's type system work, and what does `strict_types` change?

    PHP is dynamically typed, but functions, methods and properties can declare types for parameters, return values and properties, which PHP checks at run time. Supported types include scalars, classes, nullable types, union types such as int|string, intersection types and mixed.

    Coercive mode, the default: scalar arguments are converted when possible, so passing "5" to an int parameter becomes 5, and "5 apples" fails.

    Strict mode: declare(strict_types=1); at the top of a file makes calls from that file require exact scalar types, with one exception: an int is accepted where a float is expected. Passing "5" to an int parameter throws a TypeError.

    Why strict mode is recommended:

    • Bugs from silent conversions surface immediately, close to their cause.
    • Code becomes easier to analyze with static analysis tools.
    • Behavior is predictable across inputs.

    Practical tips:

    1. Add declare(strict_types=1); to every file.
    2. Type every parameter, return value and property.
    3. Convert and validate external input, such as query strings, at the boundary, since everything from HTTP arrives as strings.

    Example

    Without strict types, a numeric string is converted; with them, it throws.

    <?php
    declare(strict_types=1);
    
    function total(int $quantity, float $price): float
    {
        return $quantity * $price;
    }
    
    echo total(3, 450), "\n"; // 1350
    
    try {
        echo total("3", 450.0), "\n";
    } catch (TypeError $e) {
        echo get_class($e), "\n"; // TypeError
    }

    The integer 450 is accepted for the float parameter, which is the one conversion strict mode allows.

  4. Q4.How do `==` and `===` differ in PHP, and what are the comparison pitfalls?

    === compares both type and value without conversion. == converts operands to a common type before comparing, following rules that have caused many security and logic bugs, although PHP 8 made string-to-number comparisons much saner.

    == loose comparison=== strict comparison
    Converts types before comparingRequires the same type and value
    0 == "a" is false since PHP 8, but "1" == "01" is true"1" === "01" is false
    null == false and "" == null are truenull === false is false
    Use rarely, and only deliberatelyUse by default

    Common pitfalls:

    1. Numeric strings compare as numbers: "1e3" == "1000" is true, which breaks comparisons of tokens or hashes; use hash_equals for secrets.
    2. in_array and array_search are loose by default; pass true as the strict flag.
    3. switch uses loose comparison; match uses strict comparison.
    4. Sorting mixed types gives surprising orders; sort homogeneous data.

    Rule: use === and !== everywhere, enable strict types and convert input explicitly before comparing.

    Example

    Loose comparison treats different numeric strings as equal; strict comparison and hash_equals do not.

    <?php
    declare(strict_types=1);
    
    var_dump("1e3" == "1000");
    var_dump("1e3" === "1000");
    var_dump(in_array("1", [1, 2, 3]));
    var_dump(in_array("1", [1, 2, 3], true));
    var_dump(hash_equals("1e3", "1000"));
    // Output:
    // bool(true)
    // bool(false)
    // bool(true)
    // bool(false)
    // bool(false)
  5. Q5.How do arrays work in PHP?

    A PHP array is an ordered hash map: it stores key-value pairs, keeps insertion order and allows integer or string keys. The same structure serves as a list, a dictionary, a set, a stack or a queue.

    Key behaviors:

    • Keys: integer-like strings, such as "5", become integers; floats and booleans are cast too, which can merge keys unexpectedly.
    • Lists: an array with keys 0 to n - 1 in order is a list, checked with array_is_list.
    • Order is preserved for iteration, and functions such as sort or ksort reorder in place.
    • Copying: arrays are values; assigning copies them lazily, only when one copy is modified.

    Useful functions:

    1. array_map, array_filter and array_reduce for transformations.
    2. array_column to pull a field out of a list of records.
    3. array_key_exists versus isset: isset returns false for keys whose value is null.
    4. The spread operator [...$a, ...$b] and array_merge, which renumber integer keys, versus +, which keeps the left array's keys.

    When to use something else: for strict typing and behavior, value objects or the SPL data structures, such as SplQueue and SplPriorityQueue, are often clearer than arrays everywhere.

    Example

    Integer-like string keys become integers, isset and array_key_exists differ on null, and + keeps existing keys.

    <?php
    declare(strict_types=1);
    
    $a = ["5" => "five", "x" => null];
    echo json_encode(array_keys($a)), "\n";
    var_dump(isset($a["x"]), array_key_exists("x", $a));
    
    $left = [0 => "a", 1 => "b"];
    $right = [0 => "z", 2 => "c"];
    echo json_encode($left + $right), "\n";
    echo json_encode(array_merge($left, $right)), "\n";
    // Output:
    // [5,"x"]
    // bool(false)
    // bool(true)
    // ["a","b","c"]
    // ["a","b","z","c"]

    With +, key 0 keeps the left value a and only key 2 is added from the right, so the result is still a list; array_merge renumbers and keeps every value.

  6. Q6.How do copy-on-write and references work for PHP arrays and objects?

    Arrays and strings are values with copy-on-write: assigning an array to another variable shares the data until one of them changes, and only then is a copy made. Objects are handled through object identifiers, so assigning an object gives another handle to the same object, not a copy.

    Practical consequences:

    • Passing a large array to a function is cheap until the function modifies it.
    • Modifying an object inside a function changes the caller's object, because both refer to the same instance.
    • clone creates a shallow copy of an object; nested objects are still shared unless __clone copies them.

    References with &:

    1. function add(array &$items) lets a function modify the caller's array directly.
    2. foreach ($items as &$item) modifies elements in place, but leaves $item as a reference after the loop; reusing the variable later can silently overwrite the last element. Call unset($item) after such loops.
    3. References often make code harder to follow and can prevent copy-on-write optimizations.

    Guideline: prefer returning new values over modifying through references, and use immutable value objects for data that should not change.

    Example

    Arrays copy on change, objects are shared, and a leftover reference from foreach corrupts an array.

    <?php
    declare(strict_types=1);
    
    $original = [1, 2, 3];
    $copy = $original;
    $copy[] = 4;
    echo count($original), " ", count($copy), "\n"; // 3 4
    
    $order = new stdClass();
    $order->status = "new";
    $alias = $order;
    $alias->status = "paid";
    echo $order->status, "\n"; // paid
    
    $values = [1, 2, 3];
    foreach ($values as &$v) {
        $v *= 10;
    }
    foreach ($values as $v) {
    }
    echo json_encode($values), "\n"; // [10,20,20]

    The second loop writes each element into the last slot through the leftover reference, so 30 is lost.

  7. Q7.How do classes, interfaces, abstract classes and traits differ in PHP?

    They are four ways to share structure and behavior. Interfaces define contracts, abstract classes share partial implementations through inheritance, traits copy reusable methods into classes and concrete classes provide full implementations.

    InterfaceAbstract classTraitClass
    Declares methods and constants a type must providePartial implementation that subclasses completeReusable methods and properties copied into classesFull implementation that can be instantiated
    A class can implement manyA class can extend only oneA class can use manyCan extend one class and implement many interfaces
    Best for dependency boundaries and type hintsBest for shared logic in a closely related familyBest for small cross-cutting helpersBest for concrete behavior

    Guidelines:

    1. Type hints should use interfaces for dependencies, such as PaymentGateway, so implementations can change and tests can use fakes.
    2. Prefer composition over deep inheritance trees; inject collaborators instead of extending base classes.
    3. Use traits sparingly: they hide where methods come from and can conflict; they suit small, stateless helpers.
    4. Mark classes final unless they are designed for extension, which keeps behavior predictable.
    5. Abstract classes fit template-style code where subclasses fill in specific steps.
  8. Q8.How do enums work in PHP?

    Enums define a closed set of named values, such as order statuses. Pure enums have cases only; backed enums give each case a string or integer value, which makes them easy to store in databases and send in JSON.

    Features:

    • Type safety: a parameter typed OrderStatus accepts only its cases, not arbitrary strings.
    • Backed values: OrderStatus::from("paid") converts a stored value to a case and throws on unknown values; tryFrom returns null instead.
    • Methods and interfaces: enums can have methods, such as a human-readable label, and implement interfaces.
    • cases() lists every case, useful for validation and dropdown lists.
    • match with enums gives exhaustive handling: an unhandled case throws an error at run time, and static analysis can warn earlier.

    Good practices:

    1. Use enums instead of class constants or magic strings for fixed sets of values.
    2. Use backed enums at system boundaries, such as databases and APIs, and parse with tryFrom to handle bad input gracefully.
    3. Put state transition rules on the enum, such as which statuses can follow paid.

    Example

    A backed enum parses stored values, rejects unknown ones and holds its own transition rules.

    <?php
    declare(strict_types=1);
    
    enum OrderStatus: string
    {
        case Pending = "pending";
        case Paid = "paid";
        case Shipped = "shipped";
    
        public function canMoveTo(self $next): bool
        {
            return match ($this) {
                self::Pending => $next === self::Paid,
                self::Paid => $next === self::Shipped,
                self::Shipped => false,
            };
        }
    }
    
    $status = OrderStatus::from("paid");
    echo $status->name, "\n";
    var_dump($status->canMoveTo(OrderStatus::Shipped));
    var_dump(OrderStatus::tryFrom("lost"));
    echo implode(",", array_column(OrderStatus::cases(), "value")), "\n";
    // Output:
    // Paid
    // bool(true)
    // NULL
    // pending,paid,shipped
  9. Q9.How do readonly properties and readonly classes support immutability in PHP?

    A readonly property can be assigned once, from inside the class, usually in the constructor, and never changed afterwards. A readonly class makes all its properties readonly. Together with constructor promotion, they make immutable value objects short and safe.

    Rules:

    • A readonly property must have a type.
    • It can be initialized only once, and only from within the class scope.
    • Modifying it later throws an Error.
    • Readonly protects the property's value, but if the value is an object, that object's own properties can still change unless it is immutable too.

    "Withers" for changes: to change a value, return a new instance with the modified property, for example $money->add($other) returns a new Money. Since PHP 8.5, clone can take an array of new property values, which makes this concise.

    Why immutability helps:

    1. Value objects, such as money, email addresses and date ranges, cannot be corrupted after validation.
    2. Objects can be shared freely without defensive copies.
    3. Code is easier to reason about, especially in long-running worker processes.

    Example

    A readonly value object validates once, refuses changes and returns new instances for arithmetic.

    <?php
    declare(strict_types=1);
    
    final readonly class Money
    {
        public function __construct(public int $minor, public string $currency)
        {
            if ($minor < 0) {
                throw new InvalidArgumentException("amount cannot be negative");
            }
        }
    
        public function add(Money $other): Money
        {
            if ($other->currency !== $this->currency) {
                throw new InvalidArgumentException("currency mismatch");
            }
            return new Money($this->minor + $other->minor, $this->currency);
        }
    }
    
    $price = new Money(45000, "INR");
    $total = $price->add(new Money(5000, "INR"));
    echo $total->minor, " ", $price->minor, "\n"; // 50000 45000
    
    try {
        $price->minor = 1;
    } catch (Error $e) {
        echo $e->getMessage(), "\n"; // Cannot modify readonly property Money::$minor
    }
  10. Q10.How do constructor property promotion and named arguments simplify PHP code?

    Constructor property promotion declares and assigns properties directly in the constructor's parameter list, removing repetitive property declarations and assignments. Named arguments let callers pass arguments by parameter name, in any order, skipping optional ones.

    Constructor promotion:

    • public function __construct(private OrderRepository $orders, private int $pageSize = 20) {} declares two properties and assigns them.
    • Visibility and readonly modifiers apply as with normal properties.
    • It works well for dependency injection and value objects.

    Named arguments:

    1. Readability: new Page(size: 50, number: 2) is clearer than positional numbers.
    2. Skipping defaults: pass only the options you need, such as json_encode($data, flags: JSON_THROW_ON_ERROR).
    3. Safer refactoring of call sites with many boolean or numeric parameters.

    Caution: parameter names become part of your public interface once callers use named arguments; renaming a parameter can break them. Treat parameter names in libraries as stable.

    Example

    Promotion removes boilerplate, and named arguments skip a default and make the call self-explanatory.

    <?php
    declare(strict_types=1);
    
    final class Paginator
    {
        public function __construct(
            private int $perPage = 20,
            private int $maxPages = 100,
            private bool $includeTotal = false,
        ) {}
    
        public function describe(): string
        {
            return sprintf("per page %d, max %d, total %s", $this->perPage, $this->maxPages, $this->includeTotal ? "yes" : "no");
        }
    }
    
    echo (new Paginator())->describe(), "\n";                              // per page 20, max 100, total no
    echo (new Paginator(perPage: 50, includeTotal: true))->describe(), "\n"; // per page 50, max 100, total yes
  11. Q11.How does a `match` expression differ from a `switch` statement?

    match is an expression that returns a value, compares strictly with ===, has no fall-through between arms and throws an UnhandledMatchError when no arm matches. switch is a statement with loose comparison and fall-through that requires break.

    matchswitch
    Returns a value, so it can be assigned or returnedExecutes statements; no return value
    Strict comparisonLoose comparison
    No fall-through; several conditions share an arm with commasFalls through without break
    Throws if nothing matches and there is no defaultSilently does nothing if nothing matches

    When to use match:

    1. Mapping values to results, such as status to label or error code to HTTP status.
    2. Exhaustive handling of enum cases, where a missing case should fail loudly.
    3. Conditions with match (true), where each arm is a Boolean expression, for range checks.

    When switch still fits: several statements per case where fall-through is genuinely useful, though this is rare in modern code.

    Example

    match compares strictly, combines conditions and fails loudly on an unknown value.

    <?php
    declare(strict_types=1);
    
    function httpStatus(string $error): int
    {
        return match ($error) {
            "not_found" => 404,
            "invalid", "missing_field" => 422,
            "conflict" => 409,
        };
    }
    
    function shippingBand(float $kg): string
    {
        return match (true) {
            $kg <= 1 => "light",
            $kg <= 10 => "standard",
            default => "heavy",
        };
    }
    
    echo httpStatus("missing_field"), " ", shippingBand(4.5), "\n"; // 422 standard
    
    try {
        httpStatus("boom");
    } catch (UnhandledMatchError $e) {
        echo get_class($e), "\n"; // UnhandledMatchError
    }
  12. Q12.How do the nullsafe and null coalescing operators work in PHP?

    The null coalescing operator ?? returns its left side if it exists and is not null, otherwise its right side, without warnings for undefined keys. The nullsafe operator ?-> calls a method or reads a property only when the object is not null; otherwise the whole chain evaluates to null.

    Operators:

    • $a ?? $b: a default for missing or null values, such as $_GET["page"] ?? 1.
    • $a ??= $b: assigns only if $a is null or unset, useful for lazy initialization.
    • $a?->b()?->c: stops at the first null and returns null.

    Pitfalls:

    1. ?? treats 0, "" and false as present values; use ?: when you want any falsy value replaced, and be careful that ?: replaces 0.
    2. Nullsafe chains can hide bugs: if a value should never be null, fail loudly instead of quietly returning null.
    3. ?? on input does not validate it; convert and check types after applying defaults.

    These operators make optional data, such as an order without a shipping address, explicit and concise.

    Example

    Defaults are applied without warnings, and a missing address short-circuits the chain.

    <?php
    declare(strict_types=1);
    
    final class Address
    {
        public function __construct(public string $city) {}
    }
    
    final class Customer
    {
        public function __construct(public ?Address $address) {}
    }
    
    $query = ["limit" => "0"];
    echo $query["page"] ?? "1", " ", $query["limit"] ?? "20", "\n"; // 1 0
    
    $withAddress = new Customer(new Address("Pune"));
    $withoutAddress = new Customer(null);
    echo $withAddress->address?->city ?? "unknown", "\n";    // Pune
    echo $withoutAddress->address?->city ?? "unknown", "\n"; // unknown
  13. Q13.How do closures, arrow functions and first-class callables work in PHP?

    A closure is an anonymous function that can capture variables from its surrounding scope. Classic closures capture explicitly with use, by value unless written with &. Arrow functions, written fn ($x) => ..., capture outer variables automatically by value but can contain only a single expression.

    Forms:

    • function ($item) use ($rate) { return $item * $rate; } captures $rate by value.
    • fn ($item) => $item * $rate captures $rate automatically.
    • First-class callable syntax: strlen(...) or $service->handle(...) turns any function or method into a closure, without string names.
    • Closures can be bound to objects with bindTo or call, though this is rarely needed.

    Uses in backend code:

    1. Callbacks for array_map, array_filter and usort.
    2. Middleware pipelines, where each layer is a callable that receives the next one.
    3. Lazy factories in dependency containers.

    Cautions: variables captured by value are copied at creation time, so later changes are not seen; capture by reference only when you really need shared state.

    Example

    A value captured by use is frozen at creation, while first-class callable syntax passes a method as a callback.

    <?php
    declare(strict_types=1);
    
    final class Tax
    {
        public function __construct(private float $rate) {}
    
        public function apply(float $amount): float
        {
            return round($amount * (1 + $this->rate), 2);
        }
    }
    
    $discount = 0.1;
    $classic = function (float $p) use ($discount): float { return $p * (1 - $discount); };
    $arrow = fn (float $p): float => $p * (1 - $discount);
    $discount = 0.5;
    
    echo $classic(100), " ", $arrow(100), "\n"; // 90 90
    
    $tax = new Tax(0.18);
    echo implode(",", array_map($tax->apply(...), [100, 250])), "\n"; // 118,295
  14. Q14.How do generators work in PHP, and why are they useful for large data?

    A generator is a function that uses yield to produce values one at a time. Calling it returns an iterator, and the function's body runs only as the caller asks for the next value, pausing at each yield. Memory stays constant however many values are produced.

    Why they matter:

    • Large files: read and process a file line by line instead of loading it all.
    • Database results: stream rows from a query and process each without building a huge array.
    • Pipelines: chain generators, such as read, parse, filter and batch, where each stage pulls from the previous one.

    Features:

    1. yield $key => $value produces keys too.
    2. yield from delegates to another generator or array.
    3. Generators can receive values with send, enabling coroutine-style code, though this is rarely needed in services.
    4. return inside a generator sets a final value readable with getReturn.

    Caution: a generator can be iterated only once; and code inside it runs lazily, so errors appear during iteration, not when the generator is created.

    Example

    A pipeline of generators reads, parses and batches records while holding only one batch in memory.

    <?php
    declare(strict_types=1);
    
    function lines(string $text): Generator
    {
        foreach (explode("\n", trim($text)) as $line) {
            yield $line;
        }
    }
    
    function amounts(iterable $lines): Generator
    {
        foreach ($lines as $line) {
            [$id, $amount] = explode(",", $line);
            yield $id => (int) $amount;
        }
    }
    
    function batches(iterable $items, int $size): Generator
    {
        $batch = [];
        foreach ($items as $key => $value) {
            $batch[$key] = $value;
            if (count($batch) === $size) {
                yield $batch;
                $batch = [];
            }
        }
        if ($batch !== []) {
            yield $batch;
        }
    }
    
    $csv = "o1,100\no2,250\no3,75\no4,500\no5,40";
    foreach (batches(amounts(lines($csv)), 2) as $batch) {
        echo implode(" ", array_keys($batch)), " = ", array_sum($batch), "\n";
    }
    // Output:
    // o1 o2 = 350
    // o3 o4 = 575
    // o5 = 40
  15. Q15.How do exceptions and errors work in PHP?

    Since PHP 7, most serious problems are thrown as objects that implement Throwable. There are two branches: Exception for application-level problems, which code is expected to handle, and Error for engine-level problems, such as calling an undefined method or a type error.

    ExceptionError
    Thrown by application code and librariesThrown by the engine for programming errors
    Examples: InvalidArgumentException, RuntimeException, custom domain exceptionsExamples: TypeError, ValueError, ArithmeticError
    Catch where you can recover or translateUsually caught only at the top level for logging

    Good practices:

    1. Throw specific exceptions, including custom ones such as OrderNotFound, so callers can catch precisely.
    2. Chain causes: pass the original exception as $previous when wrapping, so stack traces show the root cause.
    3. Catch Throwable only at the top level, such as the front controller, to log and return a 500 response.
    4. Convert warnings to exceptions with an error handler, so problems such as failed file reads do not continue silently.
    5. Use finally for cleanup that must always run.

    At API boundaries, map domain exceptions to HTTP statuses, and never show stack traces to users in production.

    Example

    A low-level failure is wrapped with context, the cause is preserved and an engine error is a separate branch.

    <?php
    declare(strict_types=1);
    
    final class OrderNotFound extends RuntimeException {}
    
    function loadOrder(int $id): array
    {
        try {
            throw new PDOException("connection reset");
        } catch (PDOException $e) {
            throw new OrderNotFound("order $id could not be loaded", 0, $e);
        }
    }
    
    try {
        loadOrder(7);
    } catch (OrderNotFound $e) {
        echo $e->getMessage(), " <- ", $e->getPrevious()?->getMessage(), "\n"; // order 7 could not be loaded <- connection reset
    }
    
    try {
        intdiv(1, 0);
    } catch (Error $e) {
        echo get_class($e), " is an Exception: ", var_export($e instanceof Exception, true), "\n"; // DivisionByZeroError is an Exception: false
    }
  16. Q16.How do namespaces and autoloading work in PHP?

    Namespaces group related classes and prevent name collisions, such as App\Orders\Order and App\Billing\Order. Autoloading loads a class file automatically the first time the class is used, so applications do not need long lists of require statements.

    How autoloading works:

    1. An autoloader function is registered with spl_autoload_register.
    2. When code references an unknown class, PHP calls the autoloader with the fully qualified class name.
    3. The autoloader maps the name to a file path, usually by converting namespace separators to directory separators, and includes it.

    The common convention: a namespace prefix, such as App\, maps to a source directory, such as src/, and each class lives in a file named after it. The dependency manager generates an optimized autoloader for your code and all your packages.

    Good practices:

    • One class per file, with the file path matching the namespace.
    • Import classes with use statements at the top of the file instead of writing full names everywhere.
    • Generate an optimized class map for production, which avoids file system lookups.
    • Avoid side effects at file level, such as running code when a file is included; files should only declare things.
  17. Q17.How do you manage dependencies and versions in a PHP project?

    Declare dependencies with version constraints in the project's manifest, let the dependency manager resolve compatible versions and commit the lock file, so every environment installs exactly the same versions.

    Key practices:

    1. Version constraints: caret constraints, such as ^3.2, allow compatible minor and patch updates under semantic versioning, while the lock file pins exact versions.
    2. Commit the lock file for applications; production installs should use it, without development dependencies, and with an optimized autoloader.
    3. Separate development tools, such as test frameworks and static analysis, from runtime dependencies.
    4. Declare required PHP extensions and the PHP version, so installation fails early on incompatible servers.
    5. Audit dependencies for known vulnerabilities in continuous integration.
    6. Update regularly in small steps, reviewing changelogs for major versions.

    Supply-chain care:

    • Prefer well-maintained packages with active security response.
    • Review install scripts that packages run.
    • Use a private mirror or cache for builds, so outages and removed packages do not break deployments.

    Fewer dependencies mean fewer upgrades and less risk; PHP's standard library and extensions cover many needs.

  18. Q18.How do `self`, `static` and late static binding work in PHP?

    self refers to the class where the code is written. static refers to the class that was actually called at run time, which can be a subclass. This late static binding lets base classes write factory methods and fluent APIs that return the right subclass.

    Where the difference shows:

    • Factories: return new static() in a base class creates an instance of the subclass the method was called on; new self() always creates the base class.
    • Return types: a method declared to return static tells static analysis that subclasses return their own type, useful for fluent builders.
    • Static properties: static::$table reads a property that subclasses can override.

    Cautions with static members:

    1. Static properties are shared across all instances for the whole request, or across many requests in long-running workers, which makes them a common source of leaking state.
    2. Static method calls are hard to replace in tests; prefer injected objects for anything with side effects.
    3. Use static methods for pure functions and named constructors, such as Money::fromString("10.50").

    Example

    The same inherited factory creates the base class with self but the subclass with static.

    <?php
    declare(strict_types=1);
    
    class Model
    {
        public static function createSelf(): self
        {
            return new self();
        }
    
        public static function createStatic(): static
        {
            return new static();
        }
    }
    
    final class Order extends Model {}
    
    echo get_class(Order::createSelf()), "\n";   // Model
    echo get_class(Order::createStatic()), "\n"; // Order
  19. Q19.How do magic methods work in PHP, and when should you avoid them?

    Magic methods are special methods, with names starting with two underscores, that PHP calls automatically in certain situations. They power features such as constructors and string conversion, and they can intercept property access and method calls.

    Common magic methods:

    • __construct and __destruct for setup and cleanup.
    • __toString to convert an object to a string.
    • __get, __set, __isset and __unset for inaccessible properties.
    • __call and __callStatic for undefined methods.
    • __invoke to make an object callable like a function.
    • __clone to customize copying, and __serialize and __unserialize for serialization.

    When to avoid the interception methods:

    1. They hide behavior: __get and __call make properties and methods invisible to static analysis and editors.
    2. They are slower than real properties and methods.
    3. They turn typos into silent bugs: a misspelled property goes to __get instead of failing.

    Good uses: __invoke for single-action handlers, __toString for value objects such as email addresses, and __serialize to control what is stored. Prefer explicit properties and methods for everything else.

  20. Q20.How does PHP manage memory and garbage collection?

    PHP frees most memory through reference counting: each value tracks how many variables refer to it, and it is freed as soon as the count drops to zero. A cycle collector runs periodically to free groups of objects that reference each other but are no longer reachable from the program.

    In the request-per-process model:

    • All memory allocated during a request is released at the end of the request, so small leaks within a request rarely matter.
    • memory_limit caps memory per request; exceeding it stops the script with a fatal error.

    Where memory problems appear:

    1. Large arrays: loading a whole result set or file into memory; use generators and streaming.
    2. Long-running scripts and workers, such as queue consumers, where leaks accumulate across many jobs.
    3. Circular references in object graphs, which wait for the cycle collector.
    4. Static caches that grow without bounds in long-running processes.

    Tools: memory_get_usage and memory_get_peak_usage measure memory; profilers show allocation by function. In workers, restart the process after a number of jobs or a memory threshold as a safety net, while fixing real leaks.