{"id":1165,"date":"2026-07-26T14:37:00","date_gmt":"2026-07-26T09:07:00","guid":{"rendered":"https:\/\/lastroundai.com\/blog\/?post_type=iq&#038;p=1165"},"modified":"2026-07-21T22:32:44","modified_gmt":"2026-07-21T17:02:44","slug":"php-developer","status":"publish","type":"iq","link":"https:\/\/lastroundai.com\/interview-questions\/php-developer","title":{"rendered":"PHP Developer Interview Questions (2026): 25 Most Asked, With Answers"},"content":{"rendered":"<p>PHP still runs the majority of the web. <a href=\"https:\/\/w3techs.com\/technologies\/details\/pl-php\" target=\"_blank\" rel=\"noopener noreferrer\">W3Techs<\/a> put the number at 71.8% of websites with a known server-side language as of mid-2026, which sits oddly next to the <a href=\"https:\/\/survey.stackoverflow.co\/2025\/technology\" target=\"_blank\" rel=\"noopener noreferrer\">2025 Stack Overflow Developer Survey<\/a>, where only 18.2% of professional developers said they actually use it. That gap, most of the web running on it, a minority of developers admitting to it, is a big part of why PHP interview questions catch candidates off guard.<\/p>\n<p>Here&#8217;s an opinion that might be wrong: most PHP interview content floating around online reads like it&#8217;s stuck in 2015. It&#8217;s obsessed with <code>mysql_query()<\/code> deprecation warnings, old ternary tricks, and Zend Framework trivia nobody ships anymore. The 2026 loops candidates actually report spend far more time on password hashing, PDO, and Laravel middleware than any of that gets credit for.<\/p>\n<p>This page covers the 25 PHP interview questions that come up most across junior, mid, and senior loops: core syntax and data types, arrays and strings, OOP with traits and interfaces, superglobals and sessions, error handling, security, MySQL through PDO, Laravel basics, and one live coding question. Each answer includes what the interviewer is actually listening for, not just the textbook definition.<\/p>\n<div class=\"iq-stats not-prose\"><div class=\"iq-stat\"><span class=\"iq-stat__value\">52<\/span><span class=\"iq-stat__label\">Questions<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">8<\/span><span class=\"iq-stat__label\">Topics<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">71.8%<\/span><span class=\"iq-stat__label\">PHP web share<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Junior-Senior<\/span><span class=\"iq-stat__label\">Levels<\/span><\/div><\/div>\n<h2>Core PHP interview questions: syntax and data types<\/h2>\n<p>These show up first, often in a phone screen, to confirm you know PHP beyond copy-pasted snippets. They&#8217;re easy to answer badly if you&#8217;ve never had to explain the &#8220;why&#8221; out loud.<\/p>\n<div class=\"iq-dsec iq-dsec--easy\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"easy\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Easy questions<\/h2><span class=\"iq-dsec__n\">15<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between == and === in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Core PHP<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>==<\/code> checks whether two values are equal after PHP converts them to a common type. <code>===<\/code> checks both value and type, with no conversion. <code>\"5\" == 5<\/code> evaluates to true; <code>\"5\" === 5<\/code> evaluates to false because one side is a string and the other an integer.<\/p>\n<p>This is usually a filter question early in a phone screen. The real test comes in the follow-up: ask what <code>in_array($needle, $haystack)<\/code> returns without a third argument, and a lot of candidates forget it defaults to loose comparison, which can make a string like &#8220;0&#8221; match against a value it shouldn&#8217;t logically match at all.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What data types does PHP support, and how does type juggling work?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Core PHP<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>PHP has eight primitive types: four scalar (<code>int<\/code>, <code>float<\/code>, <code>string<\/code>, <code>bool<\/code>), two compound (<code>array<\/code>, <code>object<\/code>), and two special (<code>resource<\/code>, <code>null<\/code>). Type juggling means PHP converts a value&#8217;s type automatically based on context, so <code>\"10\" + 5<\/code> returns <code>15<\/code> as an integer.<\/p>\n<p>Interviewers dig into where juggling gets dangerous: string-to-number comparisons, and why PHP 8.0 changed non-numeric string comparisons so a string like &#8220;abc&#8221; no longer silently converts to 0 when compared against a number. If you&#8217;ve only ever worked in PHP 8, say so, and mention you know the old PHP 7 behavior was different. It signals you understand the language evolved, not just memorized the current rules.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you remove duplicate values from an array?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Arrays<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>array_unique()<\/code> removes duplicate values, comparing them as strings by default (you can pass <code>SORT_NUMERIC<\/code> or other flags as a second argument). It keeps the original array keys, so the result usually needs <code>array_values()<\/code> afterward if you need a clean, re-indexed array.<\/p>\n<p>Watch for the gotcha interviewers like to plant: <code>array_unique([1, \"1\", true])<\/code> returns just <code>[1]<\/code>, because all three values compare equal as strings. If someone asks how to dedupe by an object property or a custom rule instead, the answer is a manual loop tracking seen keys, not <code>array_unique()<\/code>, which only works cleanly on scalar-like values.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\n$fruit = [\u2019apple\u2019, \u2018banana\u2019, \u2018apple\u2019, \u2018cherry\u2019, \u2018banana\u2019];\n\n$unique = array_values(array_unique($fruit));\n\n\/\/ [\u2019apple\u2019, \u2018banana\u2019, \u2018cherry\u2019]\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you check if a string contains a substring, and what changed in PHP 8?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Strings<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Since PHP 8.0, <code>str_contains($haystack, $needle)<\/code> does exactly what it sounds like and returns a boolean. Before PHP 8, the standard was <code>strpos($haystack, $needle) !== false<\/code>, and the <code>!==<\/code> mattered: a match at position 0 returns <code>0<\/code>, which is falsy, so a loose <code>!= false<\/code> check would silently break on that one specific case.<\/p>\n<p>If a candidate answers with only the old <code>strpos()<\/code> pattern and never mentions <code>str_contains()<\/code>, a lot of interviewers assume the candidate hasn&#8217;t touched PHP in a few years. Know both, but lead with the modern one.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Explain PHP&#039;s visibility modifiers: public, protected, and private.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">OOP<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>public<\/code> members are accessible from anywhere. <code>protected<\/code> members are accessible from within the class and any subclass, but not from outside code. <code>private<\/code> members are accessible only inside the exact class that declared them, a subclass can&#8217;t touch a parent&#8217;s private property even though it inherits from that parent.<\/p>\n<p>A common trap: candidates say private members are hidden from everything, which isn&#8217;t quite right once reflection or magic methods (<code>__get<\/code>, <code>__set<\/code>) enter the picture. Mentioning that a private property can still surface through <code>var_dump()<\/code> or reflection shows you understand visibility as a language convention, not a hard security boundary.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are PHP superglobals? Name a few.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Superglobals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Superglobals are built-in PHP arrays available in every scope, functions and classes included, without needing a <code>global<\/code> keyword. The commonly tested ones: <code>$_GET<\/code>, <code>$_POST<\/code>, <code>$_SESSION<\/code>, <code>$_COOKIE<\/code>, <code>$_SERVER<\/code>, <code>$_FILES<\/code>, <code>$_REQUEST<\/code>, and <code>$_ENV<\/code>.<\/p>\n<p>Interviewers often ask why <code>$_REQUEST<\/code> is generally discouraged in production code. It merges GET, POST, and COOKIE data based on a configurable order, which means a value you expect from a form submission could actually come from a cookie an attacker controls. Being explicit about GET or POST is safer and clearer.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between a session and a cookie?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Sessions<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A cookie is stored entirely in the browser and sent to the server with every matching request. It&#8217;s client-controlled, so a user, or an attacker, can read, modify, or forge it if it isn&#8217;t signed or encrypted. A session stores the real data server-side; the browser only holds a session ID, so sensitive state like a login status never sits on the client in a readable form.<\/p>\n<p>Interviewers sometimes ask when you&#8217;d use a cookie instead of a session anyway. Remembering a &#8220;keep me logged in&#8221; preference across browser restarts is the classic case, since sessions typically expire once the browser closes unless configured otherwise.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between mysqli and PDO?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">MySQL<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>mysqli<\/code> is built specifically for MySQL and MariaDB. <code>PDO<\/code> (PHP Data Objects) is a database abstraction layer, the same API works against MySQL, PostgreSQL, SQLite, and several other drivers, so a project built on PDO can switch database engines without rewriting every query call.<\/p>\n<p>Both support prepared statements, so the security argument alone doesn&#8217;t favor either one. The practical reason most modern projects default to PDO is that abstraction, plus a cleaner object-oriented interface. mysqli still shows up in legacy codebases and a few hosting environments where PDO&#8217;s MySQL driver isn&#8217;t enabled by default.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between PDO::FETCH_ASSOC and PDO::FETCH_OBJ?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">MySQL<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>PDO::FETCH_ASSOC<\/code> returns each row as an associative array keyed by column name, accessed with <code>$row['email']<\/code>. <code>PDO::FETCH_OBJ<\/code> returns a <code>stdClass<\/code> instance instead, accessed with <code>$row->email<\/code>. Functionally they hold the same data, the difference is purely how you interact with it in the calling code.<\/p>\n<p>This rarely gets a deep follow-up on its own. It&#8217;s more often a quick check that you know PDO has fetch modes at all, before the interviewer moves on to something meatier like transactions or the difference between <code>fetch()<\/code> and <code>fetchAll()<\/code>.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does routing work in Laravel?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Laravel<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Routes are declared in <code>routes\/web.php<\/code> (session-based, for browser-facing pages) or <code>routes\/api.php<\/code> (stateless, typically token-authenticated). Each route maps an HTTP verb and a URI pattern to either a closure or a controller method. Route parameters, like <code>{id}<\/code> in <code>\/users\/{id}<\/code>, get passed as arguments to whatever handles the route.<\/p>\n<p>Interviewers sometimes ask about route model binding, where Laravel automatically resolves <code>{user}<\/code> into a full <code>User<\/code> model instance by querying the database for you, instead of writing <code>User::findOrFail($id)<\/code> manually in every controller method. It&#8217;s a small feature, but a good signal that you&#8217;ve actually built something in Laravel, not just read about it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between echo and print in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">echo vs print<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Both output a string to the response, but echo can take multiple comma-separated arguments in a single call and has no return value, while print only ever takes one argument and always returns 1, which is why print can be used inside a larger expression and echo can&#8217;t. Neither one requires parentheses, since both are language constructs rather than real functions, though people write echo(&#8216;x&#8217;) out of habit anyway.<\/p>\n<p>In practice the choice comes down to style, not capability. echo is very slightly faster in microbenchmarks because it skips setting up a return value, but that difference has never mattered for a real request. Most style guides just settle on echo since almost nobody actually relies on print&#8217;s return value.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between define() and const for creating constants in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">define vs const<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>const is a language construct resolved at compile time, so it can only appear at the top level of a script or inside a class body, and the value has to be a constant expression, not the result of calling a function. define() is a genuine function call resolved at runtime, so it can sit inside an if block or a function body, and the value can be computed at runtime, for example define(&#8216;APP_ENV&#8217;, getenv(&#8216;APP_ENV&#8217;) ?: &#8216;production&#8217;).<\/p>\n<p>const also supports visibility modifiers inside a class, public const, protected const, and private const since PHP 7.1, so you can scope a constant to a class hierarchy. define() only ever creates a global constant, there&#8217;s no way to attach it to a class or namespace it beyond a string prefix convention.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between isset(), empty(), and is_null() in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">isset empty null<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>isset($x) checks whether a variable exists and holds something other than null, it returns false both for a variable that was never set and for one explicitly set to null. empty($x) is broader, it returns true if the variable is unset, null, or holds a &#8220;falsy&#8221; value like 0, &#8216;0&#8217;, an empty string, or an empty array, so empty($x) behaves roughly like !isset($x) || !$x.<\/p>\n<p>is_null($x) only answers whether the value is literally null, it doesn&#8217;t tell you anything about whether the variable exists in the first place, and referencing an undefined variable inside is_null() will throw a warning where isset() and empty() stay quiet. For validating form input from $_POST or $_GET, isset() or array_key_exists() is almost always the right tool, is_null() gets used less often outside of checking a value that&#8217;s already guaranteed to exist.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you convert a string into an array and back in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">explode implode<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>explode($delimiter, $string) splits a string into an array on a delimiter, so explode(&#8216;,&#8217;, &#8216;a,b,c&#8217;) gives [&#8216;a&#8217;, &#8216;b&#8217;, &#8216;c&#8217;]. implode($glue, $array), also available under the alias join(), reverses that and glues the array elements back into a single string with the given separator between them.<\/p>\n<p>A gotcha worth knowing: if the delimiter never appears anywhere in the string, explode() still returns an array, just with one element containing the entire original string, it never returns an empty array or false. Passing an empty string as the delimiter argument, on the other hand, throws a ValueError as of PHP 8, it used to just emit a warning in older versions.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between an indexed array and an associative array in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">indexed vs associative<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An indexed array uses sequential integer keys starting at 0, like [&#8216;red&#8217;, &#8216;green&#8217;, &#8216;blue&#8217;], while an associative array uses keys you choose, usually strings, like [&#8216;name&#8217; => &#8216;Alice&#8217;, &#8216;role&#8217; => &#8216;admin&#8217;]. Under the hood PHP doesn&#8217;t actually treat these as separate types, both are the same ordered hash map structure, an indexed array is just an associative array whose keys happen to be sequential integers.<\/p>\n<p>foreach ($array as $value) works fine for an indexed array where you only care about the values. For an associative array you almost always want foreach ($array as $key => $value) so the loop body can use both the key and the value, since looping with just $value alone throws away the information the keys were carrying.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--medium\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"medium\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Medium questions<\/h2><span class=\"iq-dsec__n\">25<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between include, require, include_once, and require_once?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Core PHP<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>require<\/code> throws a fatal error and halts the script if the file can&#8217;t be found. <code>include<\/code> only raises a warning and keeps executing. The <code>_once<\/code> variants track which files were already loaded and skip duplicates, which matters most for files declaring classes or functions, since loading one twice would crash with a redeclaration error.<\/p>\n<p>The follow-up worth prepping: why not just use <code>require_once<\/code> everywhere? The file-tracking check adds a small overhead per call (OPcache reduces this in most production setups). Autoloading through Composer&#8217;s PSR-4 standard makes most manual <code>require<\/code> calls unnecessary anyway.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between array_map, array_filter, and array_reduce?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Arrays<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>array_map()<\/code> transforms every element with a callback and returns a new array of the same length. <code>array_filter()<\/code> runs a callback as a test and keeps only the elements that pass, so the result can be shorter than the input. <code>array_reduce()<\/code> folds the entire array into a single accumulated value, like a running total or a concatenated string.<\/p>\n<p>A sharp follow-up: what does <code>array_filter()<\/code> do if you skip the callback entirely? It drops every &#8220;falsy&#8221; value, empty strings, <code>0<\/code>, <code>null<\/code>, and <code>false<\/code> included, using PHP&#8217;s normal truthiness rules. That default surprises people who expect it to remove only <code>null<\/code>.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between an abstract class and an interface in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">OOP<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An abstract class can hold real implementation, properties, and constructors, but a class can only extend one. An interface defines only method signatures, a contract, and a class can implement as many interfaces as it needs. Interfaces can also declare constants, which has been true since early PHP 5 versions, so it doesn&#8217;t blur the distinction as much as people sometimes assume.<\/p>\n<p>The practical answer interviewers want: use an abstract class when subclasses share real behavior worth inheriting. Use an interface when unrelated classes need to guarantee the same method exists, like <code>Countable<\/code>, or a custom <code>Payable<\/code> contract shared by an <code>Invoice<\/code> class and a <code>Subscription<\/code> class that otherwise have nothing in common.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are traits, and why do they exist if PHP doesn&#039;t support multiple inheritance?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">OOP<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A trait is a set of methods a class can pull in with the <code>use<\/code> keyword. Because PHP only allows single inheritance through <code>extends<\/code>, traits give you a way to share behavior across unrelated class hierarchies without forcing them into one parent. Under the hood it behaves closer to compile-time copy-paste than real inheritance, an <code>instanceof<\/code> check against a trait name doesn&#8217;t work, since the class never actually inherits from it.<\/p>\n<p>Conflict resolution is the part that trips people up. If two traits define the same method name, PHP throws a fatal error unless you resolve it explicitly with <code>insteadof<\/code> and <code>as<\/code>. Interviewers who&#8217;ve been burned by a real trait conflict like hearing that you know this exists, even if you can&#8217;t write the exact syntax cold.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\ntrait Loggable {\n\n    public function log(string $message): void {\n\n        echo \u201c[\u201d . static::class . \u201c] \u201d . $message . PHP_EOL;\n\n    }\n\n}\ntrait Timestampable {\n\n    public function touch(): void {\n\n        $this-&gt;updatedAt = new DateTime();\n\n    }\n\n}\nclass Order {\n\n    use Loggable, Timestampable;\n    public ?DateTime $updatedAt = null;\n\n}\n$order = new Order();\n\n$order-&gt;touch();\n\n$order-&gt;log(\u2018Order updated\u2019);\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do sessions work in PHP, and where is session data stored by default?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Sessions<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Calling <code>session_start()<\/code> either creates a new session ID or resumes an existing one from the <code>PHPSESSID<\/code> cookie the browser sends back. Only that ID lives client-side. The actual data in <code>$_SESSION<\/code> lives server-side, by default in a flat file inside the directory set by <code>session.save_path<\/code>, often <code>\/tmp<\/code> on Linux.<\/p>\n<p>Senior-leaning follow-up: what happens to sessions across multiple servers behind a load balancer? File-based sessions break unless you use sticky sessions, because server B can&#8217;t read a session file written on server A. The real fix is a shared session handler, Redis or Memcached being the common choices, wired up through <code>session_set_save_handler()<\/code> or a framework driver like Laravel&#8217;s.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between an error and an exception in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Errors<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Since PHP 7, most fatal errors, calling a method on null, a type mismatch, dividing by zero, get thrown as <code>Error<\/code> objects instead of just halting the script silently. <code>Error<\/code> and <code>Exception<\/code> both implement the <code>Throwable<\/code> interface, so a single <code>catch (Throwable $e)<\/code> block can catch either. Before PHP 7, a fatal error simply couldn&#8217;t be caught at all, it just stopped execution.<\/p>\n<p>A good answer distinguishes intent: exceptions represent conditions your code anticipates and can recover from, a failed API call, invalid input. Errors generally represent programmer mistakes, calling an undefined method, that you should fix rather than gracefully paper over in most cases.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do try, catch, and finally work, and when does finally not run?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Errors<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>try<\/code> wraps code that might throw. <code>catch<\/code> handles a specific exception type, or several at once using a pipe between them (a feature since PHP 7.1), if one gets thrown. <code>finally<\/code> runs whether or not an exception was thrown or caught, which is why it&#8217;s the standard place to close a file handle or database connection.<\/p>\n<p><code>finally<\/code> almost always runs, with narrow exceptions: calling <code>exit()<\/code> or <code>die()<\/code> inside the try block, a fatal uncatchable error, or the PHP process getting killed externally. If a candidate says &#8220;finally always runs, no exceptions,&#8221; that&#8217;s technically wrong and worth a gentle correction, not just letting it slide.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you prevent SQL injection in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Security<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Use prepared statements with bound parameters, through PDO or mysqli, and never concatenate raw user input into a SQL string. Prepared statements send the query structure to the database separately from the parameter values, so even a malicious string like <code>' OR '1'='1<\/code> gets treated as literal data, not as SQL syntax.<\/p>\n<p>Interviewers occasionally push further: what about escaping with <code>mysqli_real_escape_string()<\/code> instead? It&#8217;s better than nothing, but it&#8217;s not equivalent. It handles quote characters but doesn&#8217;t cover every injection vector, and it&#8217;s easy to forget on one input path in a large codebase (the <a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/SQL_Injection_Prevention_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP SQL Injection Prevention Cheat Sheet<\/a> is the reference most senior engineers actually check). Parameterized queries remove the class of bug entirely instead of relying on someone remembering to escape every time.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between XSS and CSRF, and how do you defend against each?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Security<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>XSS injects malicious JavaScript into a page that other users then load, typically because user-supplied content got rendered without escaping. CSRF tricks a victim&#8217;s browser, which already holds valid session cookies, into submitting a request the victim never intended, like a hidden form that fires on page load.<\/p>\n<p>Defend against XSS by escaping output with <code>htmlspecialchars()<\/code> at render time, not just on input. Defend against CSRF with a random token embedded in every form and validated server-side before processing the request, this is exactly what Laravel&#8217;s <code>@csrf<\/code> directive generates automatically. A candidate who conflates the two, or thinks one fix covers both, probably hasn&#8217;t implemented either defense in a real project.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why should you never use md5() or sha1() to hash passwords?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Security<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Both are fast, general-purpose hash functions built for speed, which is exactly the wrong property for password storage. Speed is the actual enemy here: a modern GPU can compute billions of MD5 hashes per second, which makes brute-forcing or rainbow-table lookups against a leaked password database practical within hours, sometimes minutes for weak passwords.<\/p>\n<p>Password hashing needs to be deliberately slow and salted per password. That&#8217;s exactly what <code>password_hash()<\/code> does. If a candidate&#8217;s answer stops at &#8220;MD5 is insecure&#8221; without explaining why speed matters, push for the deeper reasoning, it&#8217;s the difference between memorizing a rule and understanding the actual threat model.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does password_hash() do under the hood?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Security<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>password_hash($password, PASSWORD_DEFAULT)<\/code> generates a random salt automatically, runs it through bcrypt (the current default, though PHP also supports Argon2i and Argon2id), and returns one string bundling the algorithm identifier, cost factor, salt, and hash together. You never store the salt separately, it&#8217;s baked into the returned string.<\/p>\n<p><code>password_verify($plain, $hash)<\/code> re-derives the hash using the salt and cost factor pulled from that stored string and compares the result in a timing-safe way. Interviewers sometimes ask about the cost factor specifically: raising it above the default of 10 rounds (see the <a href=\"https:\/\/www.php.net\/manual\/en\/function.password-hash.php\" target=\"_blank\" rel=\"noopener noreferrer\">official password_hash() manual page<\/a> for the full option list) makes each hash slower to compute, which is intentional. It raises the cost of brute-forcing proportionally as hardware keeps getting faster.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\n$hash = password_hash($plainPassword, PASSWORD_DEFAULT);\n\n\/\/ store $hash in the database, never the plaintext\nif (password_verify($submittedPassword, $hash)) {\n\n    \/\/ password matches\n\n} else {\n\n    \/\/ reject login\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Write a PDO prepared statement to fetch a user by email.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">MySQL<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Prepare the statement with a named placeholder, bind the actual value, execute, then fetch. The point of doing it this way instead of interpolating <code>$email<\/code> straight into the string is that the driver handles the escaping internally, it&#8217;s not something you have to get right by hand every time.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\n$stmt = $pdo-&gt;prepare(\u2018SELECT id, name, email FROM users WHERE email = :email LIMIT 1\u2032);\n\n$stmt-&gt;execute([\u2019email\u2019 =&gt; $email]);\n\n$user = $stmt-&gt;fetch(PDO::FETCH_ASSOC);\nif (!$user) {\n\n    \/\/ no matching user\n\n}\n<\/code><\/pre><\/div><\/p>\n<p>A common follow-up: what if you need multiple rows instead of one? Swap <code>fetch()<\/code> for <code>fetchAll()<\/code>, and drop the <code>LIMIT 1<\/code> if it no longer applies. Interviewers also sometimes ask whether you set <code>PDO::ATTR_ERRMODE<\/code> to <code>PDO::ERRMODE_EXCEPTION<\/code>, so a failed query throws instead of failing silently, which a surprising number of candidates forget to configure.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is Eloquent, and what&#039;s the N+1 query problem?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Laravel<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Eloquent is Laravel&#8217;s built-in ORM, following the ActiveRecord pattern where each model class maps to a table and each instance maps to a row. The N+1 problem shows up when you loop over a collection and access a relationship inside the loop, one query fetches the list, then one extra query fires per record to pull each item&#8217;s related data. A list of 50 posts each showing an author turns into 51 queries instead of 2.<\/p>\n<p>The fix is eager loading with <a href=\"https:\/\/laravel.com\/docs\/eloquent-relationships\" target=\"_blank\" rel=\"noopener noreferrer\"><code>with()<\/code><\/a>, which pulls the related data in one batched query up front instead of one per row. Interviewers who care about performance sometimes hand you a slow endpoint and ask you to spot the N+1 pattern in a query log, this shows up in real production incidents more than almost any other question on this list.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\n\/\/ N+1: fires one query per post to load its author\n\n$posts = Post::all();\n\nforeach ($posts as $post) {\n\n    echo $post-&gt;author-&gt;name;\n\n}\n\/\/ Fixed: eager load the relationship in one extra query\n\n$posts = Post::with(\u2018author\u2019)-&gt;get();\n\nforeach ($posts as $post) {\n\n    echo $post-&gt;author-&gt;name;\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Write a function that flattens a nested array of any depth.<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Coding<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Loop through the input array. For each element, check whether it&#8217;s itself an array with <code>is_array()<\/code>. If it is, recurse into it and merge the flattened result. If it isn&#8217;t, append the value directly. This handles arrays nested to any depth without knowing the depth ahead of time.<\/p>\n<p>The follow-up interviewers ask most often: what&#8217;s the time complexity here, and would you ever reach for <code>array_walk_recursive()<\/code> instead? The recursive version above is O(n) across every element at every depth. <code>array_walk_recursive()<\/code> does something similar built-in, but writing it by hand shows the interviewer you understand what the built-in function is actually doing underneath.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\nfunction flattenArray(array $items): array {\n\n    $result = [];\n    foreach ($items as $item) {\n\n        if (is_array($item)) {\n\n            $result = array_merge($result, flattenArray($item));\n\n        } else {\n\n            $result[] = $item;\n\n        }\n\n    }\n    return $result;\n\n}\n$nested = [1, [2, 3, [4, 5]], 6, [[7]]];\n\nprint_r(flattenArray($nested));\n\n\/\/ [1, 2, 3, 4, 5, 6, 7]\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the classic bug with using foreach ($array as &amp;$value) in PHP, and how do you avoid it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">foreach reference bug<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Using a reference in a foreach loop, foreach ($array as &#038;$value), leaves $value bound by reference to the last element of the array after the loop ends. If you then run a second, unrelated foreach ($array as $value) right after without unsetting $value first, that second loop silently overwrites earlier array elements, because on each iteration PHP assigns the new value into whatever $value is currently pointing at, which is still that leftover reference to the array&#8217;s last slot.<\/p>\n<p>The fix is a single line, unset($value) immediately after the reference-based loop, which breaks the binding. This bug produces no warning and no error, the array just quietly ends up with duplicated data in the wrong place, which is exactly why it survives in code review more often than bugs that actually crash.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\nforeach ($items as &amp;$value) {\n\n    $value = strtoupper($value);\n\n}\n\nunset($value); \/\/ breaks the reference\nforeach ($items as $value) {\n\n    \/\/ safe: this loop won\u2019t clobber $items anymore\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between a static variable inside a function and a static property on a class in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">static var vs property<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A static variable declared with static $count = 0; inside a function retains its value between separate calls to that function, but it&#8217;s private to that function, nothing outside the function can read it or reset it directly. It&#8217;s handy for memoizing something without polluting global state or requiring a class just to hold one counter.<\/p>\n<p>A static property, public static $count = 0; on a class, is shared across every instance of that class and is accessed through ClassName::$count rather than through any single object. Because it lives on the class itself rather than on an instance, every object sees the same value, and it exists independently of whether any instances have even been created yet. That makes static properties useful for counters or shared registries, but it also makes unit testing harder, since state leaks across test cases unless something explicitly resets it between tests.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When a closure captures a variable with use($var), is it by value or by reference, and how do arrow functions differ?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">closure use capture<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>By default, use($var) copies the outer variable&#8217;s value at the moment the closure is defined, so later changes to the outer $var don&#8217;t affect what&#8217;s inside the closure. Writing use(&#038;$var) instead captures by reference, so mutations from either side, the closure or the surrounding code, are visible on both, which is the pattern behind something like use (&#038;$total) inside an array_walk() callback that needs to accumulate a running total across calls.<\/p>\n<p>Arrow functions, fn($x) => $x + $total, introduced in PHP 7.4, automatically capture any outer variable referenced in the expression by value, so there&#8217;s no use() clause to write at all. The tradeoff is that arrow functions only support a single expression, not a full statement body, and they can&#8217;t capture by reference, so anything that needs the mutate-through-reference behavior still has to fall back to a regular closure.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are __get, __set, and __call used for in PHP, and what&#039;s the downside of relying on them too heavily?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">magic methods<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>__get($name) and __set($name, $value) fire automatically when code tries to read or write a property that&#8217;s inaccessible from outside the class or doesn&#8217;t exist at all. __call($name, $arguments) fires when code invokes a method that isn&#8217;t defined on the object. Frameworks lean on these constantly, Eloquent models use __get\/__set so attributes feel like ordinary public properties while actually routing through attribute casting, and __call is what powers dynamic query-builder style method chains.<\/p>\n<p>The cost is that overusing magic methods hides the class&#8217;s real API from IDEs, static analyzers, and anyone reading the source, since there&#8217;s no explicit list of properties or methods to look at. A typo in a property name that would normally cause an obvious fatal error instead falls into __get silently and returns null, turning a compile-time-obvious mistake into a runtime mystery that surfaces somewhere unrelated. It&#8217;s a tool for genuinely dynamic scenarios, not a substitute for declaring real properties when you already know what they are.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between DateTime and DateTimeImmutable in PHP, and when should you prefer one over the other?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">datetime immutable<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>DateTime is mutable, calling a method like ->modify(&#8216;+1 day&#8217;) or ->add() changes the object in place and returns that same instance. That&#8217;s convenient right up until you pass a DateTime object into a function expecting it to hand back a new date, and instead the caller&#8217;s original object also changed, because it was never a separate copy to begin with.<\/p>\n<p>DateTimeImmutable exposes an identical API, but every mutating-style method returns a brand new instance and leaves the original untouched, so $tomorrow = $today->modify(&#8216;+1 day&#8217;) is the only way to get the new value, $today itself is unaffected. Anywhere dates get passed between functions or stored as value objects, DateTimeImmutable is the safer default, and most current PHP code, including newer Symfony conventions, leans toward it specifically to avoid the aliasing bugs mutable date objects tend to produce.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are some common gotchas with json_encode() and json_decode() in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">json encoding gotchas<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An empty PHP array encodes to [] in JSON, but the moment the array has even one string key, PHP encodes it as a JSON object {} instead, since there&#8217;s no way for PHP to tell an empty list apart from an empty map without extra information. If you specifically need an empty PHP array to come out as {} in the JSON, you have to cast it to an object first, or pass the JSON_FORCE_OBJECT flag.<\/p>\n<p>By default json_encode() returns false on failure, say the data contains malformed UTF-8, and you have to remember to call json_last_error() afterward to find out what happened. Since PHP 7.3 you can pass JSON_THROW_ON_ERROR, which throws a JsonException instead, much easier to catch and log than checking a return value everywhere. On the decode side, json_decode($json) returns stdClass objects unless you pass true as the second argument for associative arrays, and it returns null both for actual JSON null and for invalid input, so json_last_error() matters on that side too.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\njson_encode([]);          \/\/ \u201c[]\u201d\n\njson_encode([\u2019a\u2019 =&gt; 1]);  \/\/ \u2018{\u201ca\u201d:1}\u2019\n\njson_encode((object) []); \/\/ \u201c{}\u201d\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between a greedy and a lazy quantifier in PHP&#039;s regex functions?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">regex greedy lazy<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Quantifiers like * and + in PCRE, the engine behind preg_match(), preg_replace(), and the rest, are greedy by default, they try to consume as much text as possible and only give characters back if that causes the rest of the pattern to fail. A pattern like \/&lt;.+&gt;\/ against &#8216;&lt;b&gt;bold&lt;\/b&gt;&#8217; matches the entire string from the first &lt; to the very last &gt;, because .+ greedily swallows everything in between rather than stopping at the first closing tag.<\/p>\n<p>Adding a ? after the quantifier makes it lazy, so \/&lt;.+?&gt;\/ matches as little as possible, giving you just &#8216;&lt;b&gt;&#8217; on the first match. This matters most when parsing anything with repeated delimiters, HTML tags or quoted strings, where a greedy match will happily jump over multiple delimiters and hand back a result nobody asked for. For actual HTML you should reach for a DOM parser rather than regex, but for simpler delimited text, log lines, CSV-ish formats, knowing greedy versus lazy is often what separates a correct extraction from a silently wrong one.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between usort(), uasort(), and uksort() in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">sort callbacks<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>All three sort an array using a comparator callback you supply, the difference is what happens to the keys afterward. usort() re-indexes the array from 0 once sorting finishes, discarding any existing keys, including string keys, which is fine for a plain list but destructive if the keys carried meaning, like a user ID. uasort() sorts by value the same way but preserves each element&#8217;s original key, so sorting an associative array of records by a &#8216;score&#8217; field with uasort() keeps every record attached to its original ID.<\/p>\n<p>uksort() is different again, it sorts by the keys themselves rather than the values, using your callback to compare keys instead. Across all three, the comparator itself follows the same convention, take two elements and return a negative number, zero, or a positive number to indicate their relative order, the classic C-style comparator contract.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does PSR-4 autoloading work, and what happens when a class can&#039;t be found?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">psr-4 autoloading<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>PSR-4 maps a namespace prefix to a base directory in composer.json under autoload.psr-4, for example &#8220;App\\\\&#8221;: &#8220;src\/&#8221; means App\\Services\\Mailer resolves to src\/Services\/Mailer.php. Composer registers a callback with spl_autoload_register() through vendor\/autoload.php, and PHP calls that callback the first time an unknown class name is referenced anywhere, the autoloader translates the fully qualified class name into a file path and requires it.<\/p>\n<p>If the file isn&#8217;t at the expected path, or the class or namespace declared inside it doesn&#8217;t exactly match what the mapping expected, case matters on Linux filesystems even though it doesn&#8217;t on macOS or Windows, which is a classic deploy-only bug, you get a fatal &#8220;Class not found&#8221; error. PHP never falls back to scanning the whole src\/ directory looking for the class, it only tries the specific path the mapping computed. Running composer dump-autoload -o regenerates and optimizes the class map, worth doing after moving files around or editing the autoload rules in composer.json.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why do you need mb_strlen() and the other mb_ functions instead of the plain string functions in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">multibyte strings<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>PHP&#8217;s plain string functions, strlen(), substr(), strtoupper(), and so on, operate byte by byte with no concept of character encoding. That&#8217;s fine for pure ASCII, but the moment a string contains multi-byte UTF-8 characters, accented letters, emoji, CJK text, strlen() returns a byte count rather than a character count, and substr() can cut a multi-byte character in half, producing invalid, unrenderable UTF-8 in the output.<\/p>\n<p>The mb_ extension functions, mb_strlen(), mb_substr(), mb_strtoupper(), and the rest, are encoding-aware, you can pass the encoding explicitly or rely on the internal encoding set through mb_internal_encoding(). Any application accepting input in a language other than English, or even just names with accented characters, needs to standardize on the mb_ functions for string manipulation, otherwise you get subtly corrupted text that only shows up with real production data, since English-only test fixtures never trigger the bug.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does dependency injection mean in practice, and why is it preferred over a class instantiating its own dependencies?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">dependency injection<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Dependency injection means a class receives the objects it depends on from the outside, usually through its constructor, instead of creating them itself with new. Rather than a class doing $this->mailer = new SmtpMailer() inside its constructor, it accepts a MailerInterface $mailer as a constructor argument and stores it, so whoever builds the class decides which concrete implementation to hand over.<\/p>\n<p>The practical payoff is testability. A class that hardcodes new SmtpMailer() can&#8217;t be unit tested without an actual working SMTP connection somewhere in CI. With constructor injection you pass in a fake or mock implementation of MailerInterface during tests and assert on what it received, no network call happens at all. Frameworks like Laravel and Symfony formalize this with a service container that reads type hints, or explicit config bindings, and automatically resolves and injects the right concrete class when an object is built, so wiring dependencies by hand is rarely necessary once the bindings are registered once.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--hard\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"hard\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Hard questions<\/h2><span class=\"iq-dsec__n\">12<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between self:: and static:: in PHP?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">OOP<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>self::<\/code> always resolves to the class where the code is physically written. <code>static::<\/code> uses late static binding and resolves to whichever class the method was actually called on at runtime. The difference only shows up when inheritance is involved, in a standalone class the two behave identically.<\/p>\n<p>This is a senior-level question because it&#8217;s genuinely confusing until you&#8217;ve been bitten by it once. A parent class with a factory method built on <code>self::<\/code> always returns an instance of the parent, even when called through a child class. Switch that to <code>static::<\/code> and the factory returns the correct child type. Anyone who&#8217;s built a base repository or ActiveRecord-style class in PHP has almost certainly hit this.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\nclass Model {\n\n    public static function create(): static {\n\n        return new static();\n\n    }\n    public static function createSelf(): self {\n\n        return new self();\n\n    }\n\n}\nclass User extends Model {}\n$user = User::create();      \/\/ returns instance of User\n\n$wrong = User::createSelf(); \/\/ returns instance of Model, not User\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do the PHP-FPM process manager modes static, dynamic, and ondemand differ, and how do you size pm.max_children?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">php-fpm tuning<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>PHP-FPM keeps a pool of worker processes around to handle requests, and the pm setting controls how that pool grows and shrinks. static keeps exactly pm.max_children processes running at all times, no scaling logic, giving predictable memory use but wasting RAM during idle periods and offering no headroom beyond the fixed count when traffic spikes. dynamic starts with pm.start_servers processes and scales between pm.min_spare_servers and pm.max_spare_servers based on load, the common default, though memory usage fluctuates and a sudden spike still has to wait for new workers to spawn instead of already being available. ondemand only spawns a worker when a request actually needs one and kills idle workers after pm.process_idle_timeout, minimizing idle memory footprint at the cost of a cold-start latency hit on the first request after a quiet period.<\/p>\n<p>Sizing pm.max_children comes down to available RAM divided by memory per worker. Take the server&#8217;s available memory, leaving headroom for MySQL, Redis, or anything else sharing the box, and divide by the average resident memory of a single FPM worker under real load, checked with ps aux rather than assumed from php.ini&#8217;s memory_limit. That quotient is your ceiling. Set it too high and the OOM killer starts reaping processes under load, set it too low and requests queue in the FPM backlog and time out even with spare CPU sitting idle, so the number has to come from measuring actual worker memory, not from guessing.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">A deploy ships a PHP code change but the app keeps behaving like the old code. What&#039;s likely going on?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">opcache stale cache<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>This is almost always OPcache serving stale compiled bytecode. OPcache caches each file&#8217;s compiled form keyed by file path, and by default it checks file modification timestamps, opcache.validate_timestamps=1 with an opcache.revalidate_freq interval, to decide whether to recompile. It&#8217;s common in production to set opcache.validate_timestamps=0 for a small performance win, which means OPcache never rechecks the source file at all until the FPM process restarts, so a deploy that simply overwrites files in place leaves every already-running worker serving the old cached bytecode indefinitely.<\/p>\n<p>The fix is to make cache invalidation part of the deploy step rather than depending on file-timestamp checks. That&#8217;s usually an atomic symlink swap paired with an opcache_reset() call through a small script or endpoint once the new code is in place, or a full php-fpm reload as the last deploy step, which flushes the shared memory cache along with restarting the workers. Some teams route through a load balancer with a brief drain window so no request lands on a worker mid-restart. Whatever the mechanism, the deploy pipeline needs an explicit step that resets or lets OPcache recompile, treating &#8220;the files changed on disk&#8221; as sufficient is exactly what causes this symptom.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you stop two concurrent requests from overselling the last unit of stock in a PHP checkout flow?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">optimistic vs pessimistic locking<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Pessimistic locking grabs a row lock at the database level before reading the value you&#8217;re about to check, using SELECT &#8230; FOR UPDATE inside a transaction. A second concurrent request trying to read the same row blocks until the first transaction commits or rolls back, so by the time it reads the stock count it sees the already-decremented value and correctly rejects the purchase. This is simple to reason about, but it means requests queue up waiting on the lock, which hurts throughput under heavy contention on one hot row.<\/p>\n<p>Optimistic locking skips the lock and instead ties the update&#8217;s WHERE clause to a version or the current quantity value, then checks the affected row count afterward. If zero rows were affected, someone else got there first, or stock hit zero, so the application retries the read-and-decide cycle or tells the customer the item just sold out. This scales better under contention since no lock is held while application code does other work between the read and the write, but it does require the business logic to explicitly handle the zero-rows-affected retry path, which is easy to forget and ship as a silently ignored failed update.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\n$stmt = $pdo-&gt;prepare(\n\n    \u2018UPDATE inventory SET quantity = quantity \u2013 1, version = version + 1\n\n     WHERE id = ? AND version = ? AND quantity &gt; 0\u2019\n\n);\n\n$stmt-&gt;execute([$productId, $currentVersion]);\nif ($stmt-&gt;rowCount() === 0) {\n\n    \/\/ stale version or out of stock: retry the read-decide cycle\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Can PHP&#039;s garbage collector free objects involved in a circular reference, and where do memory leaks still happen in practice?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">circular references gc<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>PHP uses reference counting as its primary memory management mechanism, an object is freed the instant its refcount hits zero. A circular reference, a Parent object holding a Child, and that Child holding a back-reference to its Parent, would never hit zero under pure reference counting alone, since each object keeps the other alive. PHP&#8217;s cycle collector, enabled by default and checkable with gc_enabled(), periodically scans for these root buffers and can detect and free genuinely unreachable cycles, so in the general case, yes, circular references do get cleaned up eventually.<\/p>\n<p>Where it still leaks in practice is long-running processes, a queue worker or daemon, where circular structures accumulate faster than the periodic collection cycle runs, since gc_collect_cycles() only fires automatically once the root buffer hits a threshold, 10,000 by default, not continuously. Closures that capture $this and then get stored back onto the object they came from are a very common source of accidental cycles nobody thinks of as circular. For a long-running worker, calling gc_collect_cycles() explicitly on an interval, or restructuring the code to avoid holding closures and objects longer than needed and letting the worker itself restart periodically, is the usual fix, rather than trusting the automatic threshold alone.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you debug a PHP script that dies with &#039;Allowed memory size exhausted&#039;?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">memory exhausted debug<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The first move isn&#8217;t to raise memory_limit and move on, that treats the symptom and just delays the crash until a slightly bigger dataset shows up. Instead, drop memory_get_peak_usage(true) calls at checkpoints through the suspect code path to bisect where memory actually balloons, passing true gives the real allocation reported by the OS rather than just what the Zend engine tracks internally, which tends to be more honest when native extensions are involved.<\/p>\n<p>A few root causes come up constantly: a buffered mysqli query pulling an entire result set into PHP memory before a single row is touched, so a two million row report query dies here even if the code only needs to stream and discard rows one at a time; array_map() or array_merge() building a huge intermediate array instead of processing in a generator or streaming fashion; and objects quietly accumulating in a loop because something, a static property, an identity map, a closure, still holds a reference to every iteration&#8217;s object. If the leak only shows up under real production load and never locally, attaching Xdebug&#8217;s profiler or a tool like Blackfire in a staging environment that mirrors production data volume is worth it, since small local datasets often just don&#8217;t reproduce the problem at all.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are backed enums in PHP 8.1, and how do they differ from a readonly property?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">php8.1 enums readonly<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A backed enum ties each case to a scalar value, int or string, declared like enum Status: string { case Active = &#8216;active&#8217;; case Suspended = &#8216;suspended&#8217;; }. Unlike the plain &#8220;pure&#8221; enums PHP 8.1 also introduced, cases with no backing value, just distinct identities, a backed enum gives you Status::Active->value for the underlying string, and Status::from() or Status::tryFrom() to build a case from a raw value coming out of a database column or an API payload, with tryFrom() returning null instead of throwing when the value doesn&#8217;t match any case. This replaces the long-standing pattern of class constants like const STATUS_ACTIVE = &#8216;active&#8217; for a fixed set of options, but with real type safety, a function typed to accept Status can&#8217;t be handed an arbitrary string by mistake the way a constant-based approach always allowed.<\/p>\n<p>readonly properties are an unrelated PHP 8.1 feature, they let you declare public readonly string $id on a class, and once set, typically inside the constructor, any later assignment throws an Error. It&#8217;s PHP&#8217;s answer to immutable value objects without needing a full DateTimeImmutable-style API, useful for DTOs where the object&#8217;s state must never change after construction. The two features pair naturally, an enum case is already immutable by nature, and a readonly property typed as an enum gives you a field that&#8217;s both constrained to a fixed set of values and can never be reassigned.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s a practical use case for PHP&#039;s Reflection API beyond academic interest?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">reflection api<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Reflection lets code inspect classes, methods, properties, and parameters at runtime, including their type hints and attributes, without the inspected code needing to cooperate or implement any special interface. The most common production use is inside dependency injection containers, when a container needs to build an object of a class it&#8217;s never seen before, it uses ReflectionClass to look at the constructor&#8217;s parameters, reads each parameter&#8217;s type hint, and recursively resolves and instantiates whatever those type-hinted dependencies are. That&#8217;s literally how Laravel&#8217;s container and PHP-DI autowire constructor arguments without hand-written binding code for every class.<\/p>\n<p>Another real use is attribute-driven behavior. PHP 8 attributes like #[Route(&#8216;\/users\/{id}&#8217;)] on a controller method are inert metadata by themselves, a framework&#8217;s router has to call ReflectionMethod::getAttributes() at boot time to actually read them and build the routing table. ORMs use the same mechanism to read column-mapping attributes on entity properties instead of maintaining a separate XML or YAML config file. The tradeoff is performance, reflection is noticeably slower than direct code, which is why frameworks leaning on it heavily cache the results, compiled container definitions, compiled routes, rather than re-reflecting the same classes on every request.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When would you use a generator instead of returning a full array in PHP, and what&#039;s the tradeoff?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">generators yield<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A generator function uses yield instead of return, and each call to it returns a Generator object that produces values lazily, one at a time, as something iterates over it, rather than computing the whole result set up front and holding it all in memory. This matters for anything working over a dataset too large to comfortably fit in memory, streaming rows out of a large database result set, parsing a multi-gigabyte log file line by line, or reading a large CSV export.<\/p>\n<p>yield from lets a generator delegate to another iterable, array or generator, inline, useful for composing generators or flattening a generator of generators without materializing the intermediate results. The tradeoff is that a generator can typically only be iterated once, since it maintains internal cursor state, if the same data needs to be looped over twice you either call the generator function again or materialize it into an array with iterator_to_array(), which defeats the memory-saving point of using a generator at all. Generators also can&#8217;t be rewound arbitrarily or accessed by index the way an array can, only the linear &#8220;give me the next value&#8221; pattern is supported.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\nfunction readLargeFile(string $path): Generator\n\n{\n\n    $handle = fopen($path, \u2018r\u2019);\n\n    while (($line = fgets($handle)) !== false) {\n\n        yield $line;\n\n    }\n\n    fclose($handle);\n\n}\nforeach (readLargeFile(\u2018access.log\u2019) as $line) {\n\n    \/\/ one line in memory at a time, regardless of file size\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you make a queue job handler idempotent so retries don&#039;t cause duplicate side effects?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">idempotent queue jobs<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Most queue systems, including Laravel&#8217;s queue workers, guarantee at-least-once delivery, not exactly-once. A job can start executing, then crash, time out, or get requeued because a worker restarted before acknowledging completion, and the queue will redeliver it. If the job&#8217;s logic is &#8220;charge the customer&#8217;s card&#8221; or &#8220;send the welcome email&#8221; with no other safeguard, a retry after a partial failure means the customer gets charged twice, or gets two welcome emails, even though the application only ever &#8220;meant&#8221; to run the job once.<\/p>\n<p>The standard fix is keying the job&#8217;s side effect on something unique to that specific business event, not the queue message ID, since a redelivered message can even get a new ID depending on the queue implementation. Before doing the actual charge or send, the job checks, inside a transaction, backed by a unique constraint rather than a plain application-level check that could itself race, whether an idempotency key, the original order ID plus an operation type, has already been recorded as processed. If it has, the job returns immediately having done nothing, treating that as success. This usually means an idempotency_keys table with a unique index on the key, and having the job&#8217;s first write inside the same transaction as the actual side effect be an insert into that table, so a concurrent or retried execution either succeeds at the insert and proceeds, or hits the unique constraint violation and knows it already ran, rather than relying on a preceding SELECT that leaves a race window between the check and the insert.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What causes a MySQL deadlock in a PHP application, and how do you design transactions to avoid it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">mysql deadlocks<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A deadlock happens when two transactions each hold a lock the other one needs, and each is waiting on the other to release it, so neither can proceed. In InnoDB this most commonly shows up when two transactions update the same set of rows in a different order, transaction A locks row 1 then tries to lock row 2, while transaction B has already locked row 2 and is trying to lock row 1, at which point MySQL&#8217;s deadlock detector picks one transaction as the victim, rolls it back, and returns error 1213 to that connection.<\/p>\n<p>The most reliable fix is enforcing a consistent lock order across every code path that touches the same rows, always acquiring locks in ascending primary key order regardless of which business operation triggered the transaction, so two concurrent transactions never end up waiting on each other in a cycle. On the PHP side, that means catching the specific deadlock error code from PDOException and retrying the whole transaction from the start a small number of times with a short backoff, rather than treating it as an unrecoverable failure, since a deadlock rollback is often successfully retried on the very next attempt once the competing transaction releases its locks. Keeping transactions short, doing the minimum work inside BEGIN\/COMMIT and avoiding any external network call while a transaction is open, also shrinks the window during which a deadlock can occur at all.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">php<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-php\">\n\ntry {\n\n    $pdo-&gt;beginTransaction();\n\n    \/\/ \u2026 update rows in a fixed, consistent order \u2026\n\n    $pdo-&gt;commit();\n\n} catch (PDOException $e) {\n\n    $pdo-&gt;rollBack();\n\n    if ($e-&gt;errorInfo[1] === 1213) {\n\n        \/\/ deadlock: safe to retry the whole transaction\n\n    }\n\n    throw $e;\n\n}\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you design rate limiting for a public PHP REST API that runs across multiple servers?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">api rate limiting<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A per-server in-memory counter, a static array or even APCu, stops working the moment there&#8217;s more than one PHP-FPM server behind a load balancer, since each server tracks its own count independently, a client would effectively get the limit multiplied by however many servers are in the pool. The counter state has to live somewhere shared and fast, which almost always means Redis.<\/p>\n<p>The usual approach is a token bucket or sliding window: on each request, middleware runs a Redis command, often a small Lua script executed atomically via EVAL so the check-and-decrement isn&#8217;t split across two round trips a race condition could slip through, that increments a counter keyed by client identifier plus a time window, checks it against the limit, and returns whether the request is allowed along with the remaining count for rate-limit response headers. Using EXPIRE on the key avoids needing a separate cleanup job for old windows. A fixed window has an edge case where a client bursts right at the boundary between two windows to effectively get double the limit, so for a true sliding window Redis sorted sets work better, storing a timestamp per request and using ZREMRANGEBYSCORE to drop entries older than the window before counting what&#8217;s left with ZCARD. The middleware sits early in the request pipeline, before any expensive application logic runs, so a rejected request costs one Redis round trip, not a full framework boot and database connection.<\/p>\n<p><\/div><\/div><\/div>\n<h2>How to prepare for a PHP interview in 2026<\/h2>\n<p>Across PHP mock interview sessions run through LastRoundAI, one pattern shows up more than any single missed question: candidates who know the right answer but can&#8217;t explain the trade-off when the interviewer pushes back. Knowing that <code>password_hash()<\/code> beats <code>md5()<\/code> is table stakes. Explaining why speed is the actual vulnerability, and what a cost factor controls, is what separates a pass from a maybe.<\/p>\n<p>A few things worth doing before the loop, in rough order of return on time invested:<\/p>\n<ul>\n<li>Rebuild two or three of the code answers above from scratch, without looking. The muscle memory for a <code>password_hash()<\/code> flow, a PDO prepared statement, and the array-flattening function covers a large share of what actually gets asked.<\/li>\n<li>If your recent experience is mostly Laravel, spend an hour on raw PDO and vanilla OOP. Interviewers use these to check whether you understand what the framework is doing for you, not just how to call an Eloquent method.<\/li>\n<li>If your recent experience is mostly vanilla PHP, skim Laravel&#8217;s routing and Eloquent docs even if the job doesn&#8217;t require it. Most PHP postings in 2026 mention Laravel by name, and interviewers often assume some familiarity even for framework-agnostic roles.<\/li>\n<li>Practice saying your security answers out loud, not just reading them. &#8220;SQL injection, use prepared statements&#8221; is a flashcard answer. Explaining why string concatenation is dangerous, in your own words, under a little time pressure, is closer to what the interview actually measures.<\/li>\n<\/ul>\n<p>None of this replaces mock practice under real time pressure. Reading an answer and producing it cold, three questions deep into a 45-minute round, are different skills entirely.<\/p>\n<p>If you want to practice these out loud before the real thing, <a href=\"\/products\/mock-interviews\">LastRoundAI&#8217;s mock interview product<\/a> runs realistic PHP rounds with follow-up questions modeled on what&#8217;s above. Once you land the interview, <a href=\"\/products\/auto-apply\">Auto-Apply<\/a> handles the volume of applications it usually takes to get there, with every send reviewed before it goes out.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>PHP still runs the majority of the web. W3Techs put the number at 71.8% of websites with a known server-side language as of mid-2026, which sits oddly next to the 2025 Stack Overflow Developer Survey, where only 18.2% of professional developers said they actually use it. That gap, most of the web running on it,&#8230;<\/p>\n","protected":false},"author":4,"featured_media":1655,"comment_status":"open","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"tags":[],"class_list":["post-1165","iq","type-iq","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>PHP Interview Questions (2026): 25 Q&amp;As | LastRoundAI<\/title>\n<meta name=\"description\" content=\"25 real PHP interview questions for 2026, from core syntax and OOP to security, PDO, and Laravel, each with a full answer and what interviewers probe.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/lastroundai.com\/interview-questions\/php-developer\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"PHP Interview Questions (2026): 25 Q&amp;As | LastRoundAI\" \/>\n<meta property=\"og:description\" content=\"25 real PHP interview questions for 2026, from core syntax and OOP to security, PDO, and Laravel, each with a full answer and what interviewers probe.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/lastroundai.com\/interview-questions\/php-developer\" \/>\n<meta property=\"og:site_name\" content=\"LastRound AI\" \/>\n<meta property=\"og:image\" content=\"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-php-developer-og.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"48 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/php-developer\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/php-developer\",\"name\":\"PHP Interview Questions (2026): 25 Q&As | LastRoundAI\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/php-developer#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/php-developer#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-php-developer-og.png\",\"datePublished\":\"2026-07-26T09:07:00+00:00\",\"description\":\"25 real PHP interview questions for 2026, from core syntax and OOP to security, PDO, and Laravel, each with a full answer and what interviewers probe.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/php-developer#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/php-developer\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/php-developer#primaryimage\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-php-developer-og.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-php-developer-og.png\",\"width\":1200,\"height\":630,\"caption\":\"PHP Developer interview questions \u2014 LastRoundAI\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/php-developer#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Interview Questions\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"PHP Developer Interview Questions (2026): 25 Most Asked, With Answers\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"name\":\"LastRound AI\",\"description\":\"Interview Assistant prep, tech careers and AI tools\",\"publisher\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\",\"name\":\"LastRound AI\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"width\":400,\"height\":400,\"caption\":\"LastRound AI\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"PHP Interview Questions (2026): 25 Q&As | LastRoundAI","description":"25 real PHP interview questions for 2026, from core syntax and OOP to security, PDO, and Laravel, each with a full answer and what interviewers probe.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/lastroundai.com\/interview-questions\/php-developer","og_locale":"en_US","og_type":"article","og_title":"PHP Interview Questions (2026): 25 Q&As | LastRoundAI","og_description":"25 real PHP interview questions for 2026, from core syntax and OOP to security, PDO, and Laravel, each with a full answer and what interviewers probe.","og_url":"https:\/\/lastroundai.com\/interview-questions\/php-developer","og_site_name":"LastRound AI","og_image":[{"width":1200,"height":630,"url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-php-developer-og.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"48 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/lastroundai.com\/interview-questions\/php-developer","url":"https:\/\/lastroundai.com\/interview-questions\/php-developer","name":"PHP Interview Questions (2026): 25 Q&As | LastRoundAI","isPartOf":{"@id":"https:\/\/lastroundai.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/lastroundai.com\/interview-questions\/php-developer#primaryimage"},"image":{"@id":"https:\/\/lastroundai.com\/interview-questions\/php-developer#primaryimage"},"thumbnailUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-php-developer-og.png","datePublished":"2026-07-26T09:07:00+00:00","description":"25 real PHP interview questions for 2026, from core syntax and OOP to security, PDO, and Laravel, each with a full answer and what interviewers probe.","breadcrumb":{"@id":"https:\/\/lastroundai.com\/interview-questions\/php-developer#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/lastroundai.com\/interview-questions\/php-developer"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/interview-questions\/php-developer#primaryimage","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-php-developer-og.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-php-developer-og.png","width":1200,"height":630,"caption":"PHP Developer interview questions \u2014 LastRoundAI"},{"@type":"BreadcrumbList","@id":"https:\/\/lastroundai.com\/interview-questions\/php-developer#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/lastroundai.com\/blog"},{"@type":"ListItem","position":2,"name":"Interview Questions","item":"https:\/\/lastroundai.com\/interview-questions"},{"@type":"ListItem","position":3,"name":"PHP Developer Interview Questions (2026): 25 Most Asked, With Answers"}]},{"@type":"WebSite","@id":"https:\/\/lastroundai.com\/blog\/#website","url":"https:\/\/lastroundai.com\/blog\/","name":"LastRound AI","description":"Interview Assistant prep, tech careers and AI tools","publisher":{"@id":"https:\/\/lastroundai.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/lastroundai.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/lastroundai.com\/blog\/#organization","name":"LastRound AI","url":"https:\/\/lastroundai.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","width":400,"height":400,"caption":"LastRound AI"},"image":{"@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1165","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq"}],"about":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/types\/iq"}],"author":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/comments?post=1165"}],"version-history":[{"count":3,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1165\/revisions"}],"predecessor-version":[{"id":1757,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/1165\/revisions\/1757"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media\/1655"}],"wp:attachment":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media?parent=1165"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/tags?post=1165"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}