PHP 8.2 wyszło oficjalnie 8 grudnia 2022. W lipcu pisałem o zapowiedziach – teraz, gdy mam finalną wersję w rękach, czas na uczciwe podsumowanie. Co faktycznie weszło do specyfikacji, co zostało odłożone i jakie są pierwsze praktyczne wrażenia z migracji projektu PHP na nową wersję.
Co weszło do PHP 8.2
Readonly Classes – działa dokładnie jak zapowiadano
<?php
declare(strict_types=1);
// Readonly class - w produkcji od 8 grudnia 2022
readonly class Address
{
public function __construct(
public string $street,
public string $city,
public string $postcode,
public string $country = 'PL'
) {}
public function format(): string
{
return "{$this->street}, {$this->postcode} {$this->city}, {$this->country}";
}
// Niemutowalne - tworzysz nowy obiekt zamiast modyfikować
public function withCity(string $city): self
{
return new self($this->street, $city, $this->postcode, $this->country);
}
}
readonly class OrderSnapshot
{
public function __construct(
public int $orderId,
public string $status,
public float $grandTotal,
public Address $shippingAddress,
public \DateTimeImmutable $createdAt
) {}
}
// Readonly classes zagnieżdżone w sobie działają poprawnie
$address = new Address('ul. Marszałkowska 1', 'Warszawa', '00-001');
$order = new OrderSnapshot(
orderId: 1042,
status: 'processing',
grandTotal: 299.99,
shippingAddress: $address,
createdAt: new \DateTimeImmutable()
);
echo $order->shippingAddress->format();
// ul. Marszałkowska 1, 00-001 Warszawa, PL
Readonly Classes – niespodziewane ograniczenie: unserialize()
Przy wdrożeniu odkryłem jeden nieoczywisty problem: readonly classes nie mogą być deserializowane przez standardowe unserialize(), bo deserializacja próbuje ustawić właściwości po konstruktorze:
<?php
readonly class Config
{
public function __construct(
public string $apiKey,
public string $endpoint
) {}
}
$config = new Config('key-123', 'https://api.example.com');
$serialized = serialize($config);
$restored = unserialize($serialized);
// Error: Cannot modify readonly property Config::$apiKey
// Rozwiązanie: implementuj __serialize() i __unserialize()
readonly class ConfigSerializable
{
public function __construct(
public string $apiKey,
public string $endpoint
) {}
public function __serialize(): array
{
return ['apiKey' => $this->apiKey, 'endpoint' => $this->endpoint];
}
public function __unserialize(array $data): void
{
// __unserialize wywołuje się przed __construct - readonly można tu ustawić
// To jedyne miejsce gdzie PHP pozwala na inicjalizację readonly po new
$this->apiKey = $data['apiKey'];
$this->endpoint = $data['endpoint'];
}
}
Nowe typy: null, false i true jako samodzielne typy
<?php
declare(strict_types=1);
// PHP 8.2 - null, false i true jako pełnoprawne typy zwracane
// (false i true były już w union types, teraz mogą stać samodzielnie)
// Przed PHP 8.2 - false w union
function findUser(int $id): User|false { /* ... */ }
// PHP 8.2 - false jako samodzielny typ (rzadko przydatne)
function isLegacyMode(): false|true { /* ... */ }
// true jako typ - funkcja która zawsze zwraca true lub rzuca wyjątek
function assertPositive(int $n): true
{
if ($n <= 0) {
throw new \InvalidArgumentException("Expected positive number, got {$n}");
}
return true;
}
// null jako samodzielny typ zwracany - funkcja nic nie zwraca
// (równoważne void, ale można użyć w union)
function logMessage(string $msg): null
{
file_put_contents('/var/log/app.log', $msg . "\n", FILE_APPEND);
return null; // lub po prostu return;
}
Nowe funkcje w bibliotece standardowej
<?php
// ini_parse_quantity() - parsowanie wartości z php.ini
$bytes = ini_parse_quantity('256M'); // 268435456
$bytes = ini_parse_quantity('2G'); // 2147483648
$bytes = ini_parse_quantity('512K'); // 524288
// Przydatne gdy sprawdzasz limity w kodzie diagnostycznym
$memoryLimit = ini_parse_quantity(ini_get('memory_limit'));
$uploadLimit = ini_parse_quantity(ini_get('upload_max_filesize'));
if ($memoryLimit < 512 * 1024 * 1024) {
echo "Ostrzeżenie: memory_limit poniżej 512M\n";
}
// curl_upkeep() - utrzymanie idle connections w curl_multi
// odFetch open connections bez nowych requestów
// mysqli_execute_query() - skrót dla prepared statement
$users = mysqli_execute_query($link, 'SELECT * FROM users WHERE status = ?', ['active']);
// Zamiast: prepare -> bind_param -> execute -> get_result
Co zostało odłożone lub nie weszło
Kilka RFC które były dyskutowane nie trafiło do PHP 8.2:
- Pipe operator (|>) - odłożony, prawdopodobnie PHP 8.3 lub 9.0
- Partial function application - wciąż w dyskusji
- readonly dla właściwości statycznych - readonly class nadal nie obsługuje statycznych właściwości
Deprecacje które mają realne znaczenie dla Magento
<?php
// 1. Dynamiczne właściwości - najważniejsza deprecacja
class DataObject // uproszczony analog Magento\Framework\DataObject
{
protected array $_data = [];
public function __get(string $name): mixed
{
return $this->_data[$name] ?? null;
}
public function __set(string $name, mixed $value): void
{
$this->_data[$name] = $value;
}
}
// DataObject i jego potomkowie w Magento MAJĄ __get/__set - ok
// Problem jest przy klasach które nie mają tych magicznych metod
class SimplePHPClass
{
public string $name = '';
}
$obj = new SimplePHPClass();
$obj->customField = 'value'; // PHP 8.2: Deprecated warning
// PHP 9.0: Fatal Error
// Magento 2.4.6+ dodało #[AllowDynamicProperties] tam gdzie potrzeba
// 2. Callables w nowym stylu - kilka form deprecated
$fn = 'strlen';
strlen('test'); // ok
$fn('test'); // ok - callable string działa
// Deprecated: ${var} interpolacja w stringach (użyj {$var})
$key = 'name';
echo "Hello ${key}"; // Deprecated
echo "Hello {$key}"; // ok
Migracja projektu na PHP 8.2 – checklist
# 1. PHPStan z levelem 8 i target PHP 8.2
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src --level=8 --php-version=8.2
# 2. PHP Compatibility Checker
composer require --dev phpcompatibility/php-compatibility
vendor/bin/phpcs --standard=PHPCompatibility \
--runtime-set testVersion 8.2 \
src/
# 3. Rector - automatyczna migracja kodu
composer require --dev rector/rector
vendor/bin/rector process src --dry-run
# 4. Testy na PHP 8.2 w CI (GitHub Actions)
# strategy:
# matrix:
# php: ['8.1', '8.2']
Rector – automatyczna migracja
<?php
// rector.php - konfiguracja automatycznej migracji
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withSets([
LevelSetList::UP_TO_PHP_82,
SetList::CODE_QUALITY,
SetList::DEAD_CODE,
])
->withPhpSets(php82: true);
// Rector automatycznie:
// - konwertuje ${var} na {$var} w stringach
// - dodaje return types tam gdzie może je wywnioskować
// - aktualizuje sygnatury metod zgodnie z PHP 8.2
Podsumowanie
PHP 8.2 to solidne wydanie – readonly classes od razu wchodzą do wszystkich nowych DTO i value objects, co znacznie redukuje boilerplate. Deprecacja dynamicznych właściwości to porządkowanie języka przed PHP 9.0 – warto przejrzeć kod pod kątem klas które polegają na tym mechaniźmie. Migracja z PHP 8.1 do 8.2 jest prosta jeśli projekt ma dobre pokrycie testami i PHPStan na poziomie 6+.
