Přeskočit na obsah
Tech-Blog Chatujme.cz Chatujme.cz
Programování

Funkce PHP pro převod mezi soustavami mají při neplatném znaku vyhodit výjimku

Čtveřice funkcí, které v PHP převádějí čísla mezi soustavami, dnes neplatný znak přeskočí a vrátí výsledek složený ze zbytku vstupu. Hlasování o návrhu, který z toho dělá výjimku, běží od 16. října do 1. listopadu a míří na PHP 8.7.

· 25 zhlédnutí

Sjoerd Langkemper otevřel 16. října hlasování o návrhu Throw exception for invalid characters in number base functions. Mění chování čtyř funkcí, které v PHP převádějí čísla mezi číselnými soustavami: bindec(), octdec(), hexdec() a base_convert(). Dnes neplatný znak přeskočí a vrátí číslo složené ze zbytku vstupu; podle návrhu mají místo toho vyhodit výjimku. Hlasování se uzavře 1. listopadu v 15:30 UTC a ke schválení je potřeba dvoutřetinová většina.

Editor s krátkým skriptem v PHP, terminál s vestavěným vývojovým serverem a výsledek v prohlížeči
Vestavěný vývojový server PHP 7.4. Právě v téhle verzi dostaly převodní funkce varování, které se mělo do PHP 8 změnit na výjimku. Foto: DirkDouse, Wikimedia Commons (CC0)

Co dnes udělá hexdec s písmenem z

Vyzkoušeli jsme to na třech verzích, které jsou k dispozici na jednom stroji: 7.4.33, 8.1.33 a 8.5.6. Chování je ve všech třech stejné.

hexdec('z');                 // 0,  k tomu varování
hexdec('1234-5678');         // 305419896, tedy 0x12345678
bindec('102');               // 2
octdec('8');                 // 0
base_convert('f', 16, 10);   // '15', 'f' je platná šestnáctková číslice
base_convert('f', 15, 10);   // '0'

Nejzrádnější je třetí řádek. Řetězec 102 není platné dvojkové číslo, protože dvojka do dvojkové soustavy nepatří. PHP ji vyhodí, zbude 10 a z toho vznikne dvojka. Výsledek je platné číslo, nic na něm není vidět a program běží dál.

Varování u toho sice padne, jenže je to E_DEPRECATED s textem „Invalid characters passed for attempted conversion, these have been ignored“. V ostrém provozu bývá tahle úroveň hlášení vypnutá nebo utopená v logu.

Některé neplatné znaky se přitom odpouštějí schválně a varování u nich nepadne vůbec. hexdec('0xff') vrátí 255, bindec('0b11') trojku a octdec('0o17') patnáctku, protože předpona zápisu je u všech tří soustav povolená. Totéž platí pro koncovou mezeru: hexdec('ff ') projde beze slova.

Výjimka schválená před sedmi lety

Dnešní návrh nic nového nevymýšlí, jen dotahuje starší rozhodnutí. V červnu 2019 prošlo hlasování o RFC Base Convert Improvements, a to v poměru 28 ku 1. Znělo doslova „Raise deprecated error in 7.4 and raise to exception in PHP 8 for unknown characters“ – tedy varování v PHP 7.4 a výjimka v PHP 8.

První polovina se splnila, druhá ne. Varování v PHP 7.4 přibylo a zůstalo tam dodnes, přes celou řadu 8.x. Varování se za výjimky vyměňuje po částech už několik vydání; v PHP 8.6 to potkalo chyby streamů. Nový návrh tenhle krok dokončuje, jen o sedm let později a s cílem PHP 8.7.

Totéž RFC z roku 2019 mělo i druhou otázku: povolit těmhle funkcím záporná čísla, aby base_convert('-ff', 16, 10) vrátilo −255. Ta skončila 2 ku 17 a neprošla. Minus je proto pořád neplatný znak jako kterýkoli jiný, přeskočí se a vyjde kladných 255 – ověřeno na PHP 8.5.6 spolu s ostatními případy.

Exception, ne ValueError

Návrh ve verzi 0.3 změnil typ vyhazované výjimky. Původně to měl být ValueError, tedy potomek Error, který se v PHP používá pro chybu v samotném programu. Autor to přehodnotil: do převodní funkce se špatný řetězec může dostat zvenku, od uživatele nebo z cizího souboru, a to není chyba kódu. Proto Exception, kterou je zvykem odchytávat.

Specifičtější třídu si návrh nechává na jindy a vysvětluje proč. Výjimky typu RangeException nebo UnexpectedValueException patří do SPL, což je z pohledu zdrojového kódu jiné rozšíření než ext/standard, odkud se tyhle funkce vyhazují. Obyčejná Exception nechává dveře otevřené, protože zúžit se dá kdykoli později.

Koho to může potkat

Návrh sám uvádí jediné legitimní použití dosavadní shovívavosti: oddělovače ve vstupu. Kód, který volá hexdec('1234-5678') a spoléhá na to, že si pomlčku PHP odmyslí, po změně skončí výjimkou. Autor k tomu dodává dvě věci – že takový kód už sedm let hlásí varování a že se oddělovač dá odstranit běžnými řetězcovými funkcemi.

Jiné funkce, které převádějí řetězec na číslo, zůstávají beze změny. base64_decode() ani intval() se návrh netýká, jsou v oddílu budoucího rozšíření. Změna se netýká ani opačného směru, tedy decbin(), decoct() a dechex(), které dostávají číslo a vracejí řetězec.

Hotová implementace čeká v pull requestu 22365, otevřeném od 19. června. Devatenáct souborů, 322 přidaných a 586 odebraných řádků – vlastní změna chování je ale jen osmnáct řádků v ext/standard/math.c, zbytek jsou testy, které se musely přepsat na nové chování.

K dnešku má hlasování 16 hlasů pro, žádný proti a žádné zdržení. Lhůta běží do 1. listopadu, tedy ještě před vydáním PHP 8.6; to má podle harmonogramu vyjít v závěru listopadu. Změna se tak nejdřív objeví až ve verzi, která po něm přijde.

Programování

PHP RFC

← zpět na výpis