Napovídání skalárních typů

Školení, která pořádám

V PHP 5 je možné v deklaraci funkce uvést u objektových parametrů třídu, ze které mají být tyto objekty vytvořeny. Totéž lze provést i s polem, s ostatními datovými typy ale ne a ani se to nechystá. Sara Golemon připravila PECL rozšíření params, které dovoluje typ parametrů určit v těle funkce zavoláním např. list($string, $int, $bool) = params_parse("sd|b");.

Mě napadlo jiné řešení – typ parametrů se dá určit v dokumentačních komentářích, se kterými umí pracovat třída Reflection (v PHP 4 by se daly najít ručně). Tyto komentáře by se daly využít i pro kontrolu nebo převod typu skutečně předaných parametrů.

Řešení bych si představoval tak, že na začátku funkce zavolám prosté check_params(), což se postará o vše potřebné. Pro řešení jsem chtěl použít „konstantu“ __FUNCTION__ a funkci func_get_args, která vrací pole parametrů předaných funkci. S konstantou __FUNCTION__ je problém ten, že vrací název aktuálně zavolané funkce a i když by se místo funkce check_params() vkládal třeba soubor check_params.inc.php, tak konstanta bude uvnitř tohoto souboru prázdná, protože se vyhodnocuje už při kompilaci. S funkcí func_get_args je zase problém ten, že vrací parametry hodnotou, i když byly předané referencí.

První problém jsem vyřešil díky funkci debug_backtrace, druhý problém nakonec tak, že se místo volání funkce vloží soubor, který může přímo nastavovat proměnné funkce, do které je vložen.

<?php
// check_params.inc.php
$_backtrace = debug_backtrace();
$_reflection = ($_backtrace[1]["class"] ? new ReflectionMethod($_backtrace[1]["class"], $_backtrace[1]["function"]) : new ReflectionFunction($_backtrace[1]["function"]));
$_function = ($_backtrace[1]["class"] ? $_backtrace[1]["class"] . "::" : "") . $_backtrace[1]["function"];
preg_match_all("~^[ \t]*\\*[ \t]*@param[ \t]+(\\S+)[ \t]+\\\$(\\S+)~m", $_reflection->getDocComment(), $_matches, PREG_SET_ORDER);
for ($_i = 0; $_i < func_num_args(); $_i++) {
    if (!isset($_matches[$_i])) {
        // missing doc comment
    } elseif (in_array($_matches[$_i][1], array("boolean", "bool", "integer", "int", "float", "double", "string", "object", "null"))) {
        settype($$_matches[$_i][2], $_matches[$_i][1]);
    } elseif ($_matches[$_i][1] == "mixed") {
        // do nothing
    } elseif ($_matches[$_i][1] == "callback") {
        if (!is_callable($$_matches[$_i][2])) {
            trigger_error("$_function(): Argument '" . $_matches[$_i][2] . "' should be a valid callback", E_USER_WARNING);
            return false;
        }
    } elseif ($_matches[$_i][1] == "resource") {
        if (!is_resource($$_matches[$_i][2])) {
            trigger_error("$_function(): Argument '" . $_matches[$_i][2] . "' is not a valid resource", E_USER_WARNING);
            return false;
        }
    } elseif ($_matches[$_i][1] == "array") {
        if (!is_array($$_matches[$_i][2])) {
            trigger_error("$_function(): Argument '" . $_matches[$_i][2] . "' must be an array, " . gettype($$_matches[$_i][2]) . " given", E_USER_ERROR);
            return false;
        }
    } else { // class
        if (get_class($$_matches[$_i][2]) != $_matches[$_i][1]) {
            trigger_error("$_function(): Argument '" . $_matches[$_i][2] . "' must be an instance of " . $_matches[$_i][1] . ", " . (is_object($$_matches[$_i][2]) ? "instance of " . get_class($$_matches[$_i][2]) : gettype($$_matches[$_i][2])) . " given", E_USER_ERROR);
            return false;
        }
    }
}

/** Ukázka použití check_params.inc.php
* @param int testovací parametr
* @return null výpis testovacího parametru s převedeným typem
*/
function check_params_test($i) {
    if (!(include "./check_params.inc.php")) {
        return false;
    }
    var_dump($i); // int(5)
}
check_params_test("5");
?>

Kód se snaží chovat stejně jako vestavěné funkce – skalární typy bez řečí převede, u neshody typů callback, resource, array a u objektů vyvolá chybu a vrátí false. Volající funkce může tento stav ošetřit podle vlastního úsudku.

K řešení problému by se dalo přistoupit ještě jedním způsobem, to popíšu zase příště.

Jakub Vrána, Řešení problému, 20.12.2007, diskuse: 5 (nové: 0)

Diskuse

ikona finc:

Je to sice hezke, ale videl bych problem ve vykonu a "ukecaneho kodu".
Pokud PHP nema typovou kontrolu, tak se s tim bohuzel stejne moc nenadela.
Jedine, co napadlo me, je obalit zakladni typy vlastnimi objekty, ktere pak mohu definovat u parametru metody.
Integer => int
String => text
Date => datum (asi nejzasadnejsi)
...

Mohu tim samozrejme ziskat i dalsi vlastnosti, jako equals, ruzne check metody, atd.

Jinak komentare jsou dobre parsovany i v PHP IDE (Eclipse). Muze to alespon pomoci pri automatickem doplnovani kodu.

dgx:

Jelikož PHP není nativní ale čistě skriptovací jazyk, byl by přístup k základních typům jakožto k objektům vůbec tím nejlepší. Potřebovalo by to ale podporu ze strany jazyka, tj. abych takové objekty mohl vytvářet přes literály a použivat na ně operátory. Takže: Sara Golemon, go go!

> Jakub: je docela vtipné, jak se silně typové jazyky snaží zavádět netypové vychytávky a slabě typové jazyky opak :-)

ikona Jakub Vrána OpenID:

Za tímto účelem je k dispozici extenze http://www.php.net/manual/en/book.spl-types.php.

optik:

pro datum a čas je v php core objekt DateTime http://maetl.coretxt.net.nz/datetime-in-php

optik:

Pořád se snaží někdo z PHP dělat co není, jak bylo v ZWS "PHP is weakly typed and will remain so", type hinting u polí a objektů plus mít takové věci v PECL je dobrý kompromis, navíc skoro pokaždé při zavádění podobných věcí do jazyka klesne výkon, vsadím se že se tak stane i pro LSB a jmenných prostorů.

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.