Příprava na pohovor PHP

Otázky k pohovoru z PHP

15 vybraných dotazů k pohovoru pro PHP backend, seskupených podle úrovně seniority. Použijte je k zopakování základů, praktických kompromisů a uvažování na seniorské úrovni pro produkční prostředí.

Spustit AI pohovor PHPNení vyžadována platební karta. K dispozici je 1 bezplatná relace.
Příprava na technické pohovory v angličtiněRežim pro lidi, pro které angličtina není rodným jazykem, určený k procvičování technických pohovorů.

Otázky pro juniory

1Jak PHP rozlišuje slabé a striktní typování a co mění declare(strict_types=1)?

PHP běžně dovoluje kompatibilní skalární převody pro deklarované typy parametrů a návratů, například číselný řetězec pro parametr int. declare(strict_types=1) zapíná striktní skalární kontroly: nekompatibilní argument či návratová hodnota vyvolá TypeError, ale int je stále přijatelný pro float. U argumentů uživatelských funkcí určuje striktnost volající soubor; návratové kontroly určuje soubor definující funkci. Volání z interních funkcí PHP mají vlastní omezení a neřídí se jednoduše strict_types vnějšího volajícího. Direktiva musí být na začátku příslušného PHP souboru. Nedělá z PHP staticky typovaný jazyk ani nevypíná převody operátorů v celém jazyce.

// THREE SEPARATE FILES: save each PHP block under its indicated filename.
// File: lib.php
<?php
function takesInt(int $x): int { return $x; }
?>

// File: weak.php
<?php
require __DIR__ . '/lib.php';
echo takesInt('123'); // 123: coercive caller
?>

// File: strict.php
<?php
declare(strict_types=1);
require __DIR__ . '/lib.php';
try {
    echo takesInt('123');
} catch (TypeError $e) {
    echo 'TypeError';
}
?>
Zkusit odpovědět na tuto otázku s trenérem AI

2Vysvětlete rozdíl mezi == a === v PHP včetně úskalí číselných řetězců, polí, objektů a in_array().

== v PHP provádí volné porovnání, případně s převodem typů; === vyžaduje shodné typy a hodnoty bez skalárních převodů. Číselné řetězce lze porovnávat číselně, proto nepoužívejte volné porovnání tam, kde je nutná přesná shoda zohledňující typ. Pole jsou podle == shodná, obsahují-li stejné dvojice klíč/hodnota podle pravidel porovnání bez ohledu na pořadí. === navíc vyžaduje stejné pořadí a typy klíčů i hodnot. Objektové == porovnává vlastnosti objektů stejné třídy; === vyžaduje stejnou instanci. in_array je výchozí volné porovnání; pro striktní kontrolu předejte true jako třetí argument. Volné porovnávání čísel a řetězců se v PHP 8 změnilo, proto nespoléhejte na historické příklady převodů bez uvedení verze.

<?php
var_dump(123 == '123');      // loose: numeric string is converted
var_dump(123 === '123');     // strict: int !== string

$values = [0, 1, 2];
var_dump(in_array('0', $values));       // loose
var_dump(in_array('0', $values, true)); // strict
Zkusit odpovědět na tuto otázku s trenérem AI

3Jak se liší návratové typy mixed, void a never v PHP a jaké záruky poskytují?

mixed dovoluje libovolnou návratovou hodnotu včetně null. void deklaruje, že funkce nevrací smysluplnou hodnotu: může dojít na konec nebo použít return;, ale nesmí vrátit výraz. Běžné volání void funkce se vyhodnotí jako null. never, dostupné od PHP 8.1, znamená, že funkce nikdy normálně nevrátí řízení volajícímu. Může vyvolat výjimku, ukončit vykonávání pomocí exit nebo zůstat v nekonečné smyčce; nemusí nakonec ukončit proces. Funkce never nesmí obsahovat příkaz return. Zásadní rozdíl: void funkce může normálně skončit a volající pokračuje, zatímco never nemá normální pokračování.

<?php
function parse(string $raw): mixed {
    return json_decode($raw, true);
}

function logMessage(string $message): void {
    error_log($message);
    return; // allowed
}

function fail(string $message): never {
    throw new RuntimeException($message);
}

logMessage('starting');
fail('stop');
echo 'unreachable';
Zkusit odpovědět na tuto otázku s trenérem AI

4Co je iterable v PHP a jak souvisí s array, Traversable, Iterator a Generator?

iterable znamená array|Traversable. Generator implementuje Iterator, který rozšiřuje Traversable, a proto splňuje iterable. Uživatelské třídy poskytují chování Traversable prostřednictvím Iterator nebo IteratorAggregate, nikoli přímou implementací Traversable v konkrétní třídě. Iterator nabízí metody řídící iteraci; IteratorAggregate poskytuje iterátor. Generátorová funkce s yield vrací Generator. Důležitý rozdíl: foreach může procházet i viditelné vlastnosti obyčejného objektu, který Traversable neimplementuje, ale takový objekt tím nesplňuje deklaraci parametru iterable.

function printItems(iterable $items): void {
    foreach ($items as $item) {
        echo $item, PHP_EOL;
    }
}

printItems(['a', 'b']);

function letters(): Generator {
    yield 'x';
    yield 'y';
}

printItems(letters());
Zkusit odpovědět na tuto otázku s trenérem AI

5Jaká jsou pravidla viditelnosti public, protected a private v PHP a jak souvisejí s dědičností a traity?

Členy public jsou dostupné všude, kde lze přistupovat k objektu nebo třídě. protected jsou dostupné v deklarující třídě a související hierarchii rodičovských a potomkovských tříd, nikoli libovolnému vnějšímu kódu. private jsou dostupné v deklarující třídě; podtřída nemůže používat soukromý člen rodiče jako zděděné rozhraní. Běžná překrývající metoda nesmí zúžit zděděnou viditelnost, ale konstruktory mají zvláštní pravidla dědičnosti a nejsou bezpodmínečným příkladem tohoto pravidla. Členy traitu se začlení do používající třídy a podléhají jejím pravidlům viditelnosti. Adaptace as může vytvořit alias, změnit viditelnost metody traitu nebo obojí; přidání aliasu samo neodstraní původní název.

<?php
class Base {
    public function a() {}
    protected function b() {}
    private function c() {}
}

trait Logs {
    public function log(string $message): void {}
}

class Child extends Base {
    use Logs { log as protected writeLog; }

    public function test(): void {
        $this->a();              // OK
        $this->b();              // OK
        // $this->c();           // Not accessible: private to Base
        $this->writeLog('ok');   // OK inside class, protected alias
    }
}

$child = new Child();
$child->a();
// $child->b();           // Not accessible from outside
// $child->writeLog('x'); // Not accessible from outside
Zkusit odpovědět na tuto otázku s trenérem AI

6Jak fungují traity v PHP a jak se řeší konflikty, když více traitů definuje stejnou metodu?

Traity umožňují horizontální opětovné použití kódu: třída importuje jejich členy pomocí use a samotný trait nelze instanciovat. Metoda deklarovaná ve třídě má přednost před metodami traitů, které mají přednost před zděděnými metodami rodiče. Poskytuje-li stejnou metodu více traitů a konflikt již neřeší metoda třídy, vyberte implementaci pomocí insteadof. Pomocí as dejte metodě traitu další název a případně jinou viditelnost. Alias původní metodu nepřejmenovává ani neodstraňuje. Vyřešení kolizí a srozumitelnost výsledného veřejného rozhraní jsou samostatné úkoly.

<?php
trait JsonLogger {
    public function log(string $message): void {
        echo "json:$message\n";
    }
}

trait FileLogger {
    public function log(string $message): void {
        echo "file:$message\n";
    }
}

class Service {
    use JsonLogger, FileLogger {
        JsonLogger::log insteadof FileLogger;
        FileLogger::log as logToFile;
        JsonLogger::log as protected logJson;
    }
}

$service = new Service();
$service->log('hello');
$service->logToFile('hello');
// $service->logJson('hello'); // not accessible from outside; protected alias
Zkusit odpovědět na tuto otázku s trenérem AI

7Co jsou to výčtové typy (enums) v PHP, včetně čistých (pure) a podložených (backed) výčtových typů, a jak zlepšují doménové modelování?

Výčtové typy (enums) v PHP definují uzavřenou množinu pojmenovaných případů pro daný typ, jako jsou stavy objednávek nebo uživatelské role. Čisté výčtové typy (pure enums) mají pouze pojmenované případy; podložené výčtové typy (backed enums) dávají každému případu unikátní řetězcovou nebo celočíselnou hodnotu, což je užitečné pro ukládání, API nebo interoperabilitu. Výčtové typy zlepšují doménové modelování tím, že nahrazují křehké raw řetězce, celá čísla nebo konstanty typově bezpečnými, samodokumentujícími hodnotami. Výčtové typy v PHP mohou mít také metody a implementovat rozhraní; všechny výčtové typy podporují výpis svých případů a podložené výčtové typy podporují převod na a z jejich skalárních hodnot.

enum OrderState
{
    case Draft;
    case Paid;
    case Shipped;
}

enum PaymentStatus: string
{
    case Pending = 'pending';
    case Complete = 'complete';
    case Failed = 'failed';
}

$status = PaymentStatus::tryFrom('complete');
var_dump($status === PaymentStatus::Complete);
Zkusit odpovědět na tuto otázku s trenérem AI

Otázky pro středně pokročilé

8Jak skalární, nullable, sjednocené, průnikové a DNF typy vyjadřují kontrakty rozhraní PHP a kdy se používají?

Deklarace PHP popisují přijímané a vracené hodnoty. Skalární deklarace int, float, string či bool určují kategorii hodnot; povolení kompatibilních skalárních převodů závisí také na strict_types a kontextu. ?T nebo T|null připouští null. Sjednocení, dostupná od PHP 8.0, připouštějí alternativy jako int|string. Čisté průniky od PHP 8.1 vyžadují, aby objekt splňoval všechny uvedené typy tříd či rozhraní, například Cacheable&JsonSerializable; int&string není platný průnik. DNF typy (disjunktivní normální forma), dostupné od PHP 8.2, kombinují alternativy a průniky v závorkách, například (Cacheable&JsonSerializable)|string. Volte nejužší srozumitelný kontrakt popisující platné vstupy místo zbytečných alternativ.

<?php
interface Cacheable {}

class Report implements Cacheable, JsonSerializable {
    public function jsonSerialize(): mixed { return []; }
}

function findName(int $id): ?string {
    return $id > 0 ? 'Alice' : null;
}

function normalize(int|string $id): string {
    return (string) $id;
}

function store(Cacheable&JsonSerializable $item): void {
    // must satisfy both interfaces
}

function handle((Cacheable&JsonSerializable)|string $input): void {
    // either an object satisfying both contracts, or a string
}
Zkusit odpovědět na tuto otázku s trenérem AI

9Jak se liší callable, Closure, prvotřídní callable, volatelné objekty a callable pole v PHP?

callable popisuje volatelné hodnoty včetně řetězců s názvy funkcí, objektů Closure, odkazů na přístupné metody a objektů s __invoke. Closure je konkrétní volatelný objekt: může pocházet z anonymní funkce nebo pojmenované funkce či metody převedené na Closure. Syntaxe prvotřídních callable, například strlen(...) či $object->method(...), vytváří Closure bez zavolání cíle. Pole [$object, 'method'] odkazuje na přístupnou instanční metodu; [ClassName::class, 'method'] v moderním PHP vyžaduje vhodnou přístupnou statickou metodu. is_callable kontroluje volatelnost v příslušném rozsahu viditelnosti. callable je široký kontrakt, Closure konkrétní reprezentace.

class Greeter {
    public function hello(string $name): string { return "Hello $name"; }
    public function __invoke(string $name): string { return "Hi $name"; }
}

$g = new Greeter();

$closure = fn(string $name) => "Hey $name";
$arrayCallable = [$g, 'hello'];
$firstClass = $g->hello(...);
$invokable = $g;

foreach ([$closure, $arrayCallable, $firstClass, $invokable] as $cb) {
    echo $cb('Sam'), PHP_EOL;
}
Zkusit odpovědět na tuto otázku s trenérem AI

10Jak PHP zpracovává přetečení celého čísla, přesnost čísel s plovoucí desetinnou čárkou a přesné desetinné výpočty?

Celočíselné typy v PHP mají velikost závislou na platformě, s limity vystavenými konstantami jako `PHP_INT_MAX`. PHP automaticky neudržuje nativní celočíselné výsledky jako celá čísla s libovolnou přesností po přetečení; přetékající celočíselná aritmetika se může stát `float` nebo jinak ztratit přesnou celočíselnou sémantiku. Typy `float` v PHP jsou binární hodnoty s plovoucí desetinnou čárkou, takže mnoho desetinných zlomků, jako například `0.1`, je pouze přibližných. Pro přesné desetinné výpočty nebo výpočty s penězi se vyhněte typům `float` a použijte přesný přístup, například menší celočíselné jednotky, kde je to vhodné, nebo nástroje pro desetinná čísla s pevnou/libovolnou přesností, jako je BCMath. Knihovna GMP je užitečná pro celočíselnou aritmetiku s libovolnou přesností.

var_dump(0.1 + 0.2 === 0.3);
printf('%.17f\n', 0.1 + 0.2);
Zkusit odpovědět na tuto otázku s trenérem AI

11Vysvětlete PHP reference (`&`), kopírování při zápisu (copy-on-write) a jak mohou reference vytvářet neočekávané vedlejší efekty.

PHP proměnné jsou interně reprezentovány jako `zvals`. Pro hodnoty, jako jsou pole a řetězce, PHP používá kopírování při zápisu (copy-on-write): přiřazení `$b = $a` obvykle sdílí stejnou základní hodnotu, dokud není jedna z proměnných modifikována, v takovém případě ji PHP oddělí/zkopíruje. Reference vytvořená pomocí `&` dělá z proměnných aliasy ke stejnému kontejneru, takže změna jedné proměnné změní i druhou a může obejít chování oddělování, které lidé očekávají od běžného přiřazení. Předávání referencí (`passing by reference`) umožňuje funkci modifikovat proměnnou volajícího. Reference mohou způsobit překvapivé vedlejší efekty, zejména u `foreach ($array as &$value)`: proměnná cyklu zůstane referencí na poslední prvek i po dokončení cyklu, takže její pozdější opětovné použití může náhodně přepsat tento prvek, pokud ji `unset($value)` nezrušíte.

$a = [1, 2];
$b = $a;      // shared until modification
$b[0] = 99;   // separates; $a is unchanged
var_dump($a, $b);

$c =& $a;     // alias/reference
$c[0] = 42;   // modifies $a too
var_dump($a, $c);
Zkusit odpovědět na tuto otázku s trenérem AI

12Jak v PHP fungují handle k objektům, přiřazení, klonování (clone), metoda `__clone`, mělká kopie (shallow copy) a hluboká kopie (deep copy)?

V PHP proměnná objektu ukládá handle (odkaz) na objekt, nikoli celou kopii objektu. Přiřazení `$b = $a` zkopíruje handle, takže obě proměnné odkazují na tentýž objekt; změna objektu prostřednictvím kterékoli proměnné je viditelná i prostřednictvím té druhé, a to bez použití `&`. Příkaz `clone $a` vytvoří novou instanci objektu s mělkou kopií vlastností původního objektu. Pokud je vlastnost sama objektem, vlastnost klonovaného objektu stále odkazuje na tentýž vnořený objekt, pokud jej explicitně neklonujete. Metoda `__clone()` je volána na novém objektu po mělké kopii a slouží k resetování ID, odpojení zdrojů nebo provedení hluboké kopie vnořených objektů. Hluboká kopie znamená rekurzivní kopírování vnořených měnitelných objektů, které by neměly být sdíleny.

class Address {
    public function __construct(public string $city) {}
}

class User {
    public function __construct(public string $name, public Address $address) {}

    public function __clone() {
        $this->address = clone $this->address; // make nested object independent
    }
}

$u1 = new User('Ann', new Address('Paris'));
$u2 = $u1;          // same object handle
$u2->name = 'Beth';
echo $u1->name, PHP_EOL;

$u3 = clone $u1;    // new User object; __clone deep-clones address
$u3->address->city = 'Rome';
echo $u1->address->city, PHP_EOL;
echo $u3->address->city, PHP_EOL;
Zkusit odpovědět na tuto otázku s trenérem AI

Otázky pro seniory

13Jak byste řešili vyčerpání databázových připojení a navrhli správu připojení pro PHP-FPM (FastCGI Process Manager) a dlouhotrvající pracovníky?

Pro řešení vyčerpání připojení bych porovnal limity databázových připojení a aktuální relace s celkovou paralelností PHP: každý aktivní podřízený proces PHP-FPM může držet své vlastní databázové připojení a více aplikačních poolů, hostitelů, cron úloh, pracovníků front, migrací a nasazení násobí poptávku. Zkontroloval bych databázové pohledy relací, stavy připojení, relace v transakci, která nic nedělá (idle-in-transaction), pomalé dotazy, čekání na zámky, chybové protokoly, metriky poolu a nedávné změny provozu nebo počtu pracovníků. Pro PHP-FPM udržujte `max_children` a paralelnost na hostitele v rámci databázového rozpočtu, používejte krátké transakce/připojení s rozsahem požadavku, provádějte commit nebo rollback ihned a vyhýbejte se držení připojení během pomalých externích volání. Perzistentní připojení PDO (PHP Data Objects) mohou snížit režii připojení, ale mohou svázat mnoho nečinných připojení na proces FPM a zachovat stav relace, takže vyžadují opatrnost. Pooler nebo proxy, jako je PgBouncer, může multiplexovat mnoho PHP klientů na méně připojení k databázovému serveru, pokud je aplikace kompatibilní. Dlouhotrvající pracovníci potřebují explicitní správu životního cyklu připojení: kontroly stavu (health checks), opětovné připojení po časových limitech nečinnosti/restartech/přepnutí při selhání (failover)/rozvětvení (forks), resetování stavu relace a opakování pouze bezpečných nebo idempotentních úloh.

Connection-budget example:
- 6 application servers
- 2 PHP-FPM pools on EACH server
- pm.max_children = 40 for EACH pool
- Assume at most 1 database connection per child

Potential request connections = 6 * 2 * 40 = 480.
If 40 were the TOTAL children per server across both pools, it would be 240.
Budget separately for queue workers, cron, deployments and administrators.
A limit of 300 database connections cannot accommodate 480 simultaneous connections.
Zkusit odpovědět na tuto otázku s trenérem AI

14Jak byste v PHP řešili čtecí repliky, rozdělení čtení a zápisu, zpoždění replikace a výpadek primární databáze?

Oddělujte připojení k primární databázi a replikám podle významu operace, nejen podle začátku SQL příkazem SELECT: zamykající čtení, zápisy a transakce vyžadující konzistenci primární databáze musí použít primární databázi. Čistě čtecí transakce mohou použít vhodnou repliku, dovolují-li to požadavky konzistence. Asynchronní replikace může mít zpoždění. Nastavení sticky v Laravelu směruje po zápisu další čtení v aktuálním požadavku na zapisovací připojení; pevná doba připnutí mezi požadavky je pouze zmírnění, nikoli bezpodmínečná záruka čtení vlastních zápisů. Tu zajistí čtení z primární databáze nebo čekání, až replika aplikuje požadovanou pozici zápisu, s časovým limitem a přechodem na primární databázi. Proxy se liší: RDS Proxy má oddělené čtecí a zapisovací endpointy a není univerzálním automatickým rozdělovníkem jednotlivých SQL příkazů. Při přepnutí po výpadku zahoďte zastaralá připojení a připojujte se znovu s omezeným prodlužováním prodlev. Ztracená odpověď na commit znamená nejasný výsledek: neopakujte neidempotentní zápisy naslepo; používejte idempotentní klíče nebo nejprve ověřte výsledek.

<?php
// config/database.php
return [
    'mysql' => [
        'read' => [
            'host' => [env('DB_REPLICA_HOST_1'), env('DB_REPLICA_HOST_2')],
        ],
        'write' => [
            'host' => [env('DB_PRIMARY_HOST')],
        ],
        'sticky'    => true,
        'driver'    => 'mysql',
        'database'  => env('DB_DATABASE'),
        'username'  => env('DB_USERNAME'),
        'password'  => env('DB_PASSWORD'),
        'charset'   => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
    ],
];
Zkusit odpovědět na tuto otázku s trenérem AI

15Jak bezpečně nasazovat databázové migrace a změny schématu v PHP aplikacích bez odstávky nebo s minimální odstávkou?

Používejte rozšíření a následné odstranění starých struktur, kompatibilní se starou i novou verzí aplikace během postupného nasazení. Nejdříve přidejte kompatibilní struktury, potom nasaďte kód využívající obě reprezentace. Potřebujete-li dvojí zápis, udržujte jeho konzistenci a navrhněte obnovitelné doplnění historických dat s omezenou rychlostí, které nepřepíše novější zápisy. Ověřte kopírovaná data a přepněte čtení před odstraněním starých cest kódu. Online DDL (jazyk definice dat) omezuje určitá rizika blokování, ale není vždy bez zámků: zkoumejte zámky dané databáze a verze, transakční omezení, spotřebu prostředků a dopad na replikaci. PostgreSQL CREATE INDEX CONCURRENTLY má například omezení a po selhání může zanechat neplatný index. Staré sloupce odstraňte až po ukončení starých instancí aplikace a požadované lhůty pro návrat. Po destruktivním odstranění struktur již návrat ke kódu vyžadujícímu odstraněná data není automaticky bezpečný.

<?php
// Step 1: Migration (Expand) -> Add new column as nullable
Schema::table('users', function (Blueprint $table) {
    $table->string('full_name')->nullable()->after('name');
});

// Step 2: Code deployment -> Write to both, read with fallback
class User extends Model {
    public function setNameAttribute($value) {
        $this->attributes['name'] = $value;
        $this->attributes['full_name'] = $value; // Dual write
    }
}

// Step 3: Background worker backfills existing rows in small batches
// Step 4: Code deployment -> Read/write exclusively from 'full_name'
// Step 5: Migration (Contract) -> Drop legacy 'name' column
Zkusit odpovědět na tuto otázku s trenérem AI