Раздел посвящён вопросам, которые проверяют глубину понимания внутренних механизмов PHP: система типов, работа ссылок, генераторы, магические методы и кооперативная многозадачность.
Система типов PHP
PHP поддерживает развитую систему типов, которая существенно усилилась начиная с PHP 7.0 и продолжает развиваться.
Скалярные типы
<?php
declare(strict_types=1);
// bool — true/false
$isActive = true;
// int — целые числа (платформозависимый размер: 64-bit на x86_64)
$count = 42;
$hex = 0x1A; // 26
$octal = 0o755; // 493 (PHP 8.1+ prefix)
$binary = 0b11111111; // 255
$underscore = 1_000_000; // Underscores for readability (PHP 7.4+)
// float — числа с плавающей точкой (IEEE 754 double precision)
$price = 19.99;
$scientific = 1.2e3; // 1200.0
// string — байтовая строка (не Unicode!)
$name = 'Alice';
$interpolated = "Hello, {$name}";
$heredoc = <<<EOT
Multi-line
string with $name interpolation
EOT;
$nowdoc = <<<'EOT'
No $interpolation here
EOT;
Ловушка собеседования: PHP строки --- последовательности байтов, а не символов.
strlen('Привет')вернёт 12 (UTF-8), а не 6. Для работы с Unicode используйтеmb_stringилиIntlChar.
Составные типы
<?php
declare(strict_types=1);
// array — ordered map (NOT a traditional array!)
$list = [1, 2, 3]; // Indexed array
$map = ['name' => 'Alice', 'age' => 30]; // Associative array
// object — instance of a class
$obj = new \stdClass();
$obj->name = 'Alice';
// callable — anything that can be called
$fn = function (int $x): int { return $x * 2; };
$fn = strlen(...); // First-class callable (PHP 8.1+)
// iterable — array | Traversable (PHP 7.1+)
function processItems(iterable $items): void
{
foreach ($items as $item) {
// Works with arrays AND iterators
}
}
Специальные типы
<?php
declare(strict_types=1);
// null — only one value: null
$nothing = null;
// void — function returns nothing (cannot be used as property type)
function logMessage(string $msg): void
{
file_put_contents('/tmp/log.txt', $msg, FILE_APPEND);
// No return, or just 'return;'
}
// never — function NEVER returns (always throws or exits)
function throwError(string $msg): never
{
throw new \RuntimeException($msg);
// Code after is unreachable
}
// mixed — any type (explicitly declared, PHP 8.0+)
// Equivalent to: string|int|float|bool|null|array|object|callable|resource
function debugDump(mixed $value): void
{
var_dump($value);
}
Union и Intersection типы
<?php
declare(strict_types=1);
// Union types (PHP 8.0+) — one of listed types
function formatId(int|string $id): string
{
return (string) $id;
}
// Intersection types (PHP 8.1+) — must satisfy ALL types
interface Countable {}
interface Serializable {}
function process(Countable&Serializable $data): void
{
// $data must implement BOTH interfaces
}
// DNF types: Disjunctive Normal Form (PHP 8.2+)
function handle((Countable&Serializable)|null $data): void
{
// Nullable intersection type
}
// Standalone types
function getId(): true { return true; } // true type (PHP 8.2+)
function check(): false { return false; } // false type (PHP 8.2+)
function empty(): null { return null; } // null type (PHP 8.2+)
Ссылки в PHP
Ссылки --- один из самых непонятых механизмов PHP. Это НЕ указатели (как в C), а псевдонимы в таблице имён переменных.
Присвоение по ссылке
<?php
declare(strict_types=1);
$a = 'hello';
$b = &$a; // $b is now an ALIAS for $a — same zval in symbol table
$b = 'world';
echo $a; // "world" — $a changed because $b is an alias
// Unsetting a reference does NOT destroy the value
unset($b);
echo $a; // "world" — $a still exists, only the alias $b is removed
Передача по ссылке
<?php
declare(strict_types=1);
function increment(int &$value): void
{
$value++;
}
$counter = 0;
increment($counter);
echo $counter; // 1 — modified in-place
// CANNOT pass a literal by reference
// increment(5); // Fatal error: Only variables can be passed by reference
// Array elements by reference
$items = [1, 2, 3];
foreach ($items as &$item) {
$item *= 2;
}
unset($item); // CRITICAL: always unset after reference foreach!
var_dump($items); // [2, 4, 6]
Возврат по ссылке
<?php
declare(strict_types=1);
class Container
{
private array $data = ['key' => 'original'];
// Returns a reference to internal data
public function &get(string $key): mixed
{
return $this->data[$key];
}
}
$container = new Container();
$ref = &$container->get('key');
$ref = 'modified';
echo $container->get('key'); // "modified" — internal state changed via reference
Критически важно для собеседования: 1) Ссылки --- НЕ указатели, а псевдонимы в таблице символов. 2)
unset($ref)удаляет псевдоним, но не значение. 3) Послеforeachс&$itemВСЕГДА делайтеunset($item), иначе последний элемент массива будет перезаписан при следующем использовании$item. 4) Нельзя передать литерал по ссылке.
Типичная ошибка с foreach по ссылке
<?php
declare(strict_types=1);
$numbers = [1, 2, 3];
foreach ($numbers as &$num) {
$num *= 10;
}
// $num is still a reference to $numbers[2]!
foreach ($numbers as $num) {
// Each iteration overwrites $numbers[2] because $num is still a reference!
}
var_dump($numbers); // [10, 20, 20] — NOT [10, 20, 30]!
// Fix: unset($num) after the first foreach
Генераторы (Generators)
Генераторы позволяют создавать итераторы без реализации интерфейса Iterator. Функция-генератор использует yield для поочерёдной отдачи значений, экономя память.
Базовое использование
<?php
declare(strict_types=1);
// Regular function — loads ALL into memory
function getAllNumbers(int $max): array
{
$numbers = [];
for ($i = 1; $i <= $max; $i++) {
$numbers[] = $i;
}
return $numbers; // 10M items = ~160 MB RAM
}
// Generator — yields one at a time
function generateNumbers(int $max): \Generator
{
for ($i = 1; $i <= $max; $i++) {
yield $i; // Pauses here, resumes on next iteration
}
}
// Memory-efficient iteration
foreach (generateNumbers(10_000_000) as $number) {
// Constant memory usage — only ONE value in memory at a time
if ($number > 5) break;
}
yield с ключами и значениями
<?php
declare(strict_types=1);
function readCsvRows(string $filePath): \Generator
{
$handle = fopen($filePath, 'r');
if ($handle === false) {
throw new \RuntimeException("Cannot open file: {$filePath}");
}
try {
$lineNumber = 0;
while (($row = fgetcsv($handle)) !== false) {
// yield key => value
yield $lineNumber => $row;
$lineNumber++;
}
} finally {
fclose($handle);
}
}
// Usage
foreach (readCsvRows('/tmp/data.csv') as $line => $columns) {
echo "Line {$line}: " . implode(', ', $columns) . "\n";
}
yield from и send()
<?php
declare(strict_types=1);
// Delegate to another generator with yield from
function innerGenerator(): \Generator
{
yield 'a';
yield 'b';
return 'inner-result'; // Return value accessible via getReturn()
}
function outerGenerator(): \Generator
{
yield 'start';
$result = yield from innerGenerator(); // Delegates to innerGenerator
echo "Inner returned: {$result}\n"; // "inner-result"
yield 'end';
}
foreach (outerGenerator() as $value) {
echo $value . "\n"; // start, a, b, end
}
// send() — send values INTO generator
function accumulator(): \Generator
{
$total = 0;
while (true) {
$value = yield $total; // Receive value from send(), yield current total
if ($value === null) break;
$total += $value;
}
}
$gen = accumulator();
$gen->current(); // Initialize: 0
$gen->send(10); // Total: 10
$gen->send(20); // Total: 30
echo $gen->send(5); // 35
На собеседовании часто спрашивают: Зачем нужны генераторы? Основные сценарии: 1) Обработка больших файлов построчно. 2) Пагинация из БД. 3) Бесконечные последовательности. 4) Pipeline-обработка данных. Генератор --- это прерываемая функция, которая помнит своё состояние.
Магические методы
Магические методы --- специальные методы с двойным подчёркиванием, которые вызываются PHP автоматически при определённых действиях с объектом.
Конструктор и деструктор
<?php
declare(strict_types=1);
final class DatabaseConnection
{
private \PDO $pdo;
public function __construct(
private readonly string $dsn,
private readonly string $user,
private readonly string $password,
) {
$this->pdo = new \PDO($dsn, $user, $password);
echo "Connection opened\n";
}
public function __destruct()
{
// Called when object is garbage collected or script ends
echo "Connection closed\n";
// $this->pdo is automatically released
}
}
Перехват доступа к свойствам
<?php
declare(strict_types=1);
final class DynamicConfig
{
private array $data = [];
// Called when reading an inaccessible property
public function __get(string $name): mixed
{
return $this->data[$name] ?? throw new \OutOfBoundsException(
"Property '{$name}' does not exist"
);
}
// Called when writing to an inaccessible property
public function __set(string $name, mixed $value): void
{
$this->data[$name] = $value;
}
// Called by isset() or empty() on an inaccessible property
public function __isset(string $name): bool
{
return isset($this->data[$name]);
}
// Called by unset() on an inaccessible property
public function __unset(string $name): void
{
unset($this->data[$name]);
}
}
$config = new DynamicConfig();
$config->database = 'mysql'; // __set()
echo $config->database; // __get() → "mysql"
echo isset($config->database); // __isset() → true
unset($config->database); // __unset()
Перехват вызовов методов
<?php
declare(strict_types=1);
final class QueryBuilder
{
private array $conditions = [];
// Called when invoking inaccessible instance methods
public function __call(string $name, array $arguments): self
{
if (str_starts_with($name, 'where')) {
$field = lcfirst(substr($name, 5)); // whereEmail → email
$this->conditions[$field] = $arguments[0] ?? null;
return $this;
}
throw new \BadMethodCallException("Method {$name} does not exist");
}
// Called when invoking inaccessible static methods
public static function __callStatic(string $name, array $arguments): self
{
$instance = new self();
return $instance->__call($name, $arguments);
}
public function getConditions(): array
{
return $this->conditions;
}
}
$query = new QueryBuilder();
$query->whereEmail('[email protected]')->whereName('Alice');
// $conditions: ['email' => '[email protected]', 'name' => 'Alice']
$query2 = QueryBuilder::whereStatus('active');
// Static call handled by __callStatic
__toString, __invoke, __clone
<?php
declare(strict_types=1);
final class Money
{
public function __construct(
private int $amount,
private string $currency,
) {}
// Called when object is used as string
public function __toString(): string
{
return number_format($this->amount / 100, 2) . ' ' . $this->currency;
}
// Called when object is used as a function
public function __invoke(float $multiplier): self
{
return new self(
(int) ($this->amount * $multiplier),
$this->currency,
);
}
// Called after clone — customize cloning behavior
public function __clone(): void
{
// Perform deep copy of nested objects if needed
// In this case, only scalars — nothing to do
}
}
$price = new Money(1999, 'USD');
echo $price; // __toString() → "19.99 USD"
echo (string) $price; // Same: "19.99 USD"
$doubled = $price(2.0); // __invoke() → Money(3998, 'USD')
echo $doubled; // "39.98 USD"
$copy = clone $price; // __clone() called after shallow copy
Сериализация: __serialize / __unserialize
<?php
declare(strict_types=1);
final class SecureSession
{
public function __construct(
private string $userId,
private string $token,
private \DateTimeImmutable $expiresAt,
) {}
// Modern approach (PHP 7.4+) — replaces __sleep/__wakeup
public function __serialize(): array
{
return [
'userId' => $this->userId,
'token' => $this->token,
'expiresAt' => $this->expiresAt->format('c'),
// Sensitive data can be excluded
];
}
public function __unserialize(array $data): void
{
$this->userId = $data['userId'];
$this->token = $data['token'];
$this->expiresAt = new \DateTimeImmutable($data['expiresAt']);
}
// __debugInfo — controls var_dump() output
public function __debugInfo(): array
{
return [
'userId' => $this->userId,
'token' => '***HIDDEN***',
'expiresAt' => $this->expiresAt->format('Y-m-d H:i:s'),
];
}
}
$session = new SecureSession('user-1', 'secret-token', new \DateTimeImmutable('+1 hour'));
$serialized = serialize($session); // Calls __serialize()
$restored = unserialize($serialized); // Calls __unserialize()
var_dump($session); // __debugInfo() — token is hidden
Для собеседования:
__serialize()/__unserialize()(PHP 7.4+) --- современная замена__sleep()/__wakeup(). Они имеют приоритет: если определены обе пары, используются__serialize()/__unserialize().__debugInfo()контролирует выводvar_dump().
Приведение типов и строгое сравнение
== vs === (жонглирование типами)
<?php
declare(strict_types=1);
// Loose comparison (==) — type juggling
var_dump(0 == 'foo'); // false (PHP 8.0+), was true in PHP 7!
var_dump(0 == ''); // false (PHP 8.0+), was true in PHP 7!
var_dump('' == null); // true (!)
var_dump('0' == false); // true (!)
var_dump('' == false); // true (!)
var_dump(null == false); // true (!)
var_dump(0 == null); // true (!)
var_dump('1' == true); // true
// Strict comparison (===) — no type conversion
var_dump(0 === false); // false — different types
var_dump('' === null); // false — different types
var_dump(1 === true); // false — different types
var_dump('1' === '1'); // true — same type, same value
// Spaceship operator <=> for sorting
var_dump(1 <=> 2); // -1
var_dump(2 <=> 2); // 0
var_dump(3 <=> 2); // 1
declare(strict_types=1)
<?php
declare(strict_types=1);
function addInts(int $a, int $b): int
{
return $a + $b;
}
// With strict_types=1:
// addInts(1.5, 2); // TypeError: must be int, float given
// addInts('1', 2); // TypeError: must be int, string given
addInts(1, 2); // OK: 3
// IMPORTANT: strict_types affects the FILE where the CALL is made
// Not the file where the function is defined!
Ключевой момент:
strict_typesвлияет на файл, в котором происходит ВЫЗОВ функции, а не на файл с определением. В режимеstrict_types=1автоматическое приведение скалярных типов отключается, и передача неверного типа вызываетTypeError.
Fibers (Кооперативная многозадачность)
Fibers (PHP 8.1+) --- механизм кооперативной многозадачности. Fiber --- это блок кода, который может быть приостановлен и возобновлён, сохраняя свой стек вызовов.
Базовое использование
<?php
declare(strict_types=1);
$fiber = new \Fiber(function (): void {
$value = \Fiber::suspend('first'); // Pause and return 'first'
echo "Received: {$value}\n"; // "Received: hello"
$value2 = \Fiber::suspend('second'); // Pause and return 'second'
echo "Received: {$value2}\n"; // "Received: world"
});
// Start the fiber
$result1 = $fiber->start();
echo "Fiber yielded: {$result1}\n"; // "Fiber yielded: first"
// Resume with a value
$result2 = $fiber->resume('hello');
echo "Fiber yielded: {$result2}\n"; // "Fiber yielded: second"
// Resume again
$fiber->resume('world');
// Fiber completes
echo $fiber->isTerminated() ? 'Done' : 'Running'; // "Done"
Практический пример: асинхронные задачи
<?php
declare(strict_types=1);
final class Scheduler
{
/** @var \SplQueue<\Fiber> */
private \SplQueue $queue;
public function __construct()
{
$this->queue = new \SplQueue();
}
public function schedule(\Fiber $fiber): void
{
$this->queue->enqueue($fiber);
}
public function run(): void
{
while (!$this->queue->isEmpty()) {
$fiber = $this->queue->dequeue();
if (!$fiber->isStarted()) {
$fiber->start();
} elseif ($fiber->isSuspended()) {
$fiber->resume();
}
// If fiber is not terminated, reschedule
if (!$fiber->isTerminated()) {
$this->queue->enqueue($fiber);
}
}
}
}
// Create tasks
$task1 = new \Fiber(function (): void {
echo "Task 1: Step 1\n";
\Fiber::suspend();
echo "Task 1: Step 2\n";
});
$task2 = new \Fiber(function (): void {
echo "Task 2: Step 1\n";
\Fiber::suspend();
echo "Task 2: Step 2\n";
});
$scheduler = new Scheduler();
$scheduler->schedule($task1);
$scheduler->schedule($task2);
$scheduler->run();
// Output:
// Task 1: Step 1
// Task 2: Step 1
// Task 1: Step 2
// Task 2: Step 2
Fiber API
<?php
declare(strict_types=1);
$fiber = new \Fiber(function (): string {
\Fiber::suspend();
return 'completed';
});
// Fiber lifecycle methods
$fiber->start(); // Start execution
$fiber->resume('data'); // Resume suspended fiber with data
$fiber->getReturn(); // Get return value (only after termination)
// Fiber state inspection
$fiber->isStarted(); // Has start() been called?
$fiber->isRunning(); // Is currently executing?
$fiber->isSuspended(); // Is paused via suspend()?
$fiber->isTerminated(); // Has finished execution?
// Static method — can only be called INSIDE a fiber
\Fiber::suspend($value); // Pause current fiber, optionally pass value back
Для собеседования: Fibers --- НЕ потоки и НЕ параллельное выполнение. Это кооперативная многозадачность: fiber сам решает, когда отдать управление через
suspend(). Fibers используются библиотеками (ReactPHP, Revolt) для построения async API. В пользовательском коде они редко используются напрямую.