Jan 10 2010

Změny vestavěných vyjímek v PHP 5.3

Tag: English,PHPJens @ 23:00

Tento článke je k dispozici pouze v jazyce English.


Nov 10 2009

PHP: break a goto řídící stuktury

Tag: PHPJens @ 23:50

Zase jsem se dnes naučil něco nového o PHP. Věděli jste, že klíčové slovo break může mít číselný argument? Já se přiznám že jsme to netušil a nikdy mě to ani nenapadlo. Kolega v práci to zřejmě běžně používá, takže díky Pauci za objev :) A jak to celé funguje a na co to je? Volitelný celočíselný argument udává, z jaké hloubky zanoření v řídící struktuře má vyskočit, implicitní je 1, podívejte se na příklad:

<?php
$i = 0;
while (++$i) {
    switch ($i) {
    case 5:
        echo "At 5<br />n";
        break 1;  /* vyskoci pouze ze switch. */
    case 10:
        echo "At 10; quitting<br />n";
        break 2;  /* vyskoci ze switch a while. */
    default:
        break;
    }
}
?>

Více přímo v manuálu PHP.

Další zajímavostí je řídící struktura goto. Ta je nově přidána od PHP 5.3, osobně si myslím ze goto je čisté zlo, ale určitě existuje případ, kdy se může hodit. Co mě ale zaujalo nejvíce, přímo v manuálové stránce PHP je naprosto překvapivě umístěn obrázek (s tím jsem se v manuálu PHP nikdy nesetkal), který je navíc pojatý jako komiks a zřejmě vyjadřuje názor autora(ů) manuálové stránky na přidání struktury goto do jazyka :)

goto

Zdroj obrázku: xkcd.


Aug 06 2009

Upgrade databáze a použití LAST_INSERT_ID()

Tag: MySQL,PHP,Zend FrameworkJens @ 10:40

Při vytváření nových verzí/revizí nějaké aplikace (ať již pomocí subversion či jiného nástroje) je často vhodné a někdy i potřebné aktualizovat strukturu databáze (přidání nových tabulek, sloupců, atd.) případně aktualizovat data (číselníky, systémové hodnoty) v databázi tak, aby nová změna v kódu byla kompatibilní se změnou struktury či dat v databázi (pro úplnost pouze doplním, že článek je napsán pro databázi MySQL 5.x).

Otázka je, jak tuto změnu provést tak, aby jsme při aktualizaci projektu na příslušnou revizi měli k dispozici i aktuální verzi databáze. Jedním z jednodušších řešení je, že spolu s commitem upraveného kódu (například nějakého modelu) commitneme i skript, který po aktualizaci projektu na příslušnou revizi spustíme a tím zajistíme upgrade databáze.

Tento skript může mít různé podoby, buď ho můžeme napsat přímo v PHP a nebo v případě jednodušší aktualizace použijeme pouze několik SQL příkazů. Nedávno jsem zrovna řešil poměrně jednoduchý problém, bylo třeba přidat skript do databáze šablonu i s jednou jazykovou mutací — to znamená: přidat jeden řádek do tabulky sablona a jeden řádek do tabulky sablona_mutace. Jelikož je však při definici ID šablon i mutací použit extra typ AUTO_INCREMENT, tak vložení druhého řádku s mutací bude záviset na ID vloženého řádku šablony. V SQL skriptu nemůžu přímo ID definovat, protože v době kdy se bude skript (upgrade) provádět může mnou vybrané ID šablony už být použito, proto je potřeba nechat sloupec NULL a MySQL vloží vlastní ID dle hodnoty AUTO_INCREMENT tabulky.

Řešením tohoto problému je použití funkce LAST_INSERT_ID(), po vložení řádku šablony definujeme cizí klíč ID šablony v řádku mutace právě pomocí této funkce. Výsledek by pak mohl vypadat nějak takto:

INSERT INTO sablona (id, nazev, kategorie, datum)
    VALUES (NULL, 'Neuskutečněná schůzka', 'system', NOW());

INSERT INTO sablona_mutace (id, sablona_id, jazyk_id, telo)
   VALUES (NULL, LAST_INSERT_ID(), 1, 'Schůzka ne neuskutečnila ...');

Takto jednouše by mohl vypada SQL skript, PHP skript kterým by jste udělali to samé například v Zend_Frameworku by mohl vypadat přibližně takto:

...
$sablona = array(
  'nazev' => 'Neuskutečněná schůzka',
  'kategorie' => 'system',
  'datum' => new Zend_Db_Expr('NOW()'),
);
$sablona_id = Sablona::getInstance()->insert($sablona);

$mutace = array(
  'sablona_id' => $sablona_id,
  'jazyk_id' => 1,
  'telo' => 'Schůzka ne neuskutečnila ...',
);
$mutace_id = SablonaMutace::getInstance()->insert($mutace);
...

Poznámka k PHP příkladu: modely jsou potomky třídy Zend_Db_Table a jsou vytvořeny jako singleton; hodnoty sloupce kategorie a jazyk_id v příkladu jsou magic konstanty což není úplně programátorsky čisté — je to pouze pro jednoduchost příkladu.


Jun 29 2009

Nastavení PHP pro větší bezpečnost

Tag: PHPJens @ 23:30

O tom, jak zvýšit bezpečnost PHP aplikací již bylo napsáno moc a moc. Prvním a základním pravidel je filtrování vstupů uživatelů, na toto téma již bylo popsáno nepřeberné množství přístupů a naprogramováno spousty knihoven, které dělají téměř vše za vás. Druhým, neméně důležitým aspektem je konfigurace PHP direktiv, ať již v přímo v php.ini nebo pomocí ini_set(...) či v htaccessu. To, na jaké direktivy by jste se měli zaměřit především, se pokouší odpovědět i PHP Security Consortium ve svém projektu PhpSecInfo.

PhpSecInfo

PhpSecInfo se zaměřuje na řadu klíčových direktiv, které jsou z hlediska bezpečné konfigurace PHP důležité, doporučuje jejich nastavení, popisuje důvody proč je nastavit tak a tak a poskytuje vám přehlednou aplikaci v PHP na to, aby jste si sami mohly vyzkoušet nastavení vašeho serveru či virtualhostu.

Pojďme se blíže na jednotlivé testy a k nim odpovídající direktivy podívat (seznam testů je řazen abecedně, nikoliv podle důležitosti):

  • allow_url_fopen
    - defaultně on, doporučená hodnota off
    - v případě že je povoleno, může skript používat funkce file_get_contents(), include(), require(), … na vzdálené soubory, což může způsobit vložení závadného kódu z cizího serveru pokud správně neošetříte vstupy
    - více o allow_url_fopen direktivě/testu
  • allow_url_include
    - defaultně 0, doporučená hodnota 0
    - podobný problém jako allow_url_fopen
    - více o allow_url_include direktivě/testu
  • display_errors
    - defaultně on, doporučená hodnota na produkčním serveru off
    - vhodné mít zapnuté při ladění a vývoji, na produkčním serveru se doporučuje vypnout
    - více o display_errors direktivě/testu
  • expose_php
    - defaultně on, doporučená hodnota off
    - rozhodně vypnout vždy, umožňuje potenciálnímu útočníkovi zjistit relativně přesně verzi PHP na serveru a na základě toho využít potencionální bezpečnostní chyby
    - více o expose_php direktivě/testu
  • file_support
    - v tomto případě se nejedná o konfigurační direktivu ale pouze název testu, který ověřuje zda vaše verze PHP obsahuje opravu problému zranitelnosti v knihovně cURL
    - více o file_support testu
  • file_uploads
    - defaultně on, v případě že ve vaší aplikaci nahrávání souborů nepoužíváte, doporučuje se nastavit off
    - více o file_uploads direktivě/testu
  • force_redirect
    - defaultně on, doporučená hodnota on
    - direktiva, která se používá pouze v případě že spouštíte PHP přes CGI, na některých web serverech vypnutá (IIS)
    - více o force_redirect direktivě/testu
  • group_id
    - nejedná se o direktivu PHP ale proměnou skriptu, tato proměnná definuje ID skupiny systému pod kterým je PHP skript spuštěn a tento test upozorňuje na možné riziko spuštění skriptu privilegovaným/systémovým uživatelem
    - více o group_id testu
  • magic_quotes_gpc
    - defaultně on, doporučená hodnota off
    - velice známá a v minulosti často diskutovaná direktiva – je-li direktiva zapnuta, pak budou všechny vstupní proměnné _GET, _POST, _COOKIE (a možná i některé další) escapovány pomocí addslashes
    - direktiva magic_quotes_gpc byla přídána především pro zabránění SQL injection, nicméně později se ukázalo, že funkce addslashes není zcela dostačující pro některé speciální případy a proto se musí používat speciální escapovací funkce v závislosti na použité databázi, spoléhat tedy na „auto-escapování“ není dobré a proto se doporučuje direktivu nezapínat
    - více o magic_quotes_gpc direktivě/testu
  • memory_limit
    - maximální povolená hodnota paměti, kterou může PHP skript alokovat – doporučenou hodnotu těžko definovat, obecně čím méně tím lépe nicméně je třeba brát v úvahu „žravost“ některých knihoven typu Zend Framework, které si s pamětí starosti nedělají a alokují co mohou
    - více o memory_limit direktivě/testu
  • open_basedir
    - tato direktiva by měla nahradit safe_mod, který je zlem vždy a všude – direktiva open_basedir definuje, do kterých adresářů na fyzickém filesystému se PHP skripty dostanou a kam už ne
    - v případě hostingového serveru nebo serveru s více uživateli, je správně nastavený open_basedir nezbytnost
    - více o open_basedir direktivě/testu
  • post_max_size
    - defaultně 8 MB, doporučená hodnota 256 K
    - definuje jakou maximální velikost dat lze odeslat PHP skriptu pomocí metody POST – doporučená hodnota je zde poměrně nízká a lze o ni dosti polemizovat, vždy je třeba zvážit potřeby aplikace, příliš vysoká hodnota může znamenat možnost DoS útoku na server
    - více o post_max_size direktivě/testu
  • register_globals
    - defaultně off, doporučená hodnota off
    - tuto direktivu snad není třeba ani popisovat – jedná se o historický pozůstatek z divokých dob PHP, zakázat vždy a všude – více o register_globals direktivě/testu
  • save_path
    - tato direktiva (přesněji session.save_path) definuje cestu, kde se budou ukládat data uživatelských session – cesta by měla být mimo strukturu kořene webu (document root) a měla by mít vhodně nastavená oprávnění (aby si session nemohli číst ostatní uživatelé systému)
    - více o save_path direktivě/testu
  • upload_max_filesize
    - definuje jakou maximální velikost dat lze odeslat PHP skriptu pomocí nahrávacího (upload) formuláře – doporučená hodnota musí vycházet z potřeb konkrétní aplikace, obecně čím méně tím lépe
    - více o upload_max_filesize direktivě/testu
  • upload_tmp_dir
    - definuje cestu, kde se ukládají dočasně soubory, které jsou nahrávány na server pomocí formuláře – podobně jako direktiva save_path by měla být tato cesta mimo strukturu kořene webu a měla by mít vhodně nastavená oprávnění
    - více o upload_tmp_dir direktivě/testu
  • user_id
    - nejedná se o direktivu PHP ale proměnou skriptu, tato proměnná definuje ID uživatele systému pod kterým je PHP skript spuštěn a tento test upozorňuje na možné riziko spuštění skriptu privilegovaným/systémovým uživatelem
    - více o file_uploads testu

Popsané výchozí nastavení se vždy nemusí shodovat v vašim výchozím nastavení, některé distribuce a operační systémy a některé verze PHP si nastavují tyto hodnoty různě.

Před nastavením dané proměnné je vždy také třeba zvážit, zda bude server či VirtualHost pro více projektů/uživatelů. Důležité je také zvážit, jestli nasazení omezujících pravidel v podobě změn PHP direktiv neovlivní současné projekty na serveru, které by se pak museli předělat vzhledem ke změně nastavení. Proto je vhodné použít kombinaci jak globálních nastavení v php.ini, tak lokálních pomocí ini_set(...) či htaccessu.

Závěr

Bohužel, projekt PhpSecInfo už je přes dva roku opuštěn (poslední verze je z 2007/04/06) a těžko říci, zda kdy bude ještě aktualizován. Myslím že je to škoda, protože stále vznikají nové a nové hrozby, přidávají se nové konfigurační direktivy (a některé se ruší) a pro začínajícího ale i pokročilého administrátora mohl být projekt dobrým zdrojem informací a inspirace.


Jan 08 2009

GoogleBot a HTTP status 503

Tag: PHP,SEO,Zend FrameworkJens @ 23:55

Občas je třeba provést na stránkách údržbu tak, že je třeba na chvíli zastavit zobrazování webu – ať už je to údržba databáze nebo cokoliv jiného. V takovém případě se změní například index.php aby zobrazoval „nějakou hlášku o údržbě“. Problém ale může nastat, když zrovna v tu chvíli indexuje váš obsah třeba GoogleBot. Vaše hláška o údržbě pro něj bude obyčejná informace vrácená typicky s hlavičkou HTTP statusu 200 (viz. RFC HTTP status 200) a GoogleBot stránku pěkně zaindexuje a pak se nestačíte divit co že to google vrací za výsledky na vašem webu :)

Na toto existuje doporučení přímo od Googlu: „… nastavte server tak, aby vrátil status 503 (viz. RFC HTTP status 503) namísto statusu 200 …“. V PHP jednoduchá věc pomocí funkce header(), ideálně ještě doplníme vlastní hlášku o údržbě:

<?php
header('HTTP/1.1 503 Service Temporarily Unavailable');
header('Status: 503 Service Temporarily Unavailable');
?>
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>503 Service Temporarily Unavailable</title>
</head><body><h1>Service Temporarily Unavailable</h1>
<p>The server is temporarily unable to service your request due to maintenance downtime or
capacity problems. Please try again later.</p>
</body></html>

Retry-After hlavička

Navíc, podle RFC – Header Field Definitions je může být použito v hlavičce u statusu 503 pole Retry-After:

The Retry-After response-header field can be used with a 503 (Service Unavailable) response to indicate how long the service is expected to be unavailable to the requesting client. This field MAY also be used with any 3xx (Redirection) response to indicate the minimum time the user-agent is asked wait before issuing the redirected request. The value of this field can be either an HTTP-date or an integer number of seconds (in decimal) after the time of the response.
Retry-After = "Retry-After" ":" ( HTTP-date | delta-seconds ) Two examples of its use are Retry-After: Fri, 31 Dec 1999 23:59:59 GMT Retry-After: 120 In the latter example, the delay is 2 minutes.

Tedy, navíc ještě přidáte hlavičku Retry-After s hodnotou (v sekundách, nebo přímo časem), za kterou předpokládáte že web pojede – na příklad za 1 hodinu (3600s):

header('HTTP/1.1 503 Service Temporarily Unavailable');
header('Status: 503 Service Temporarily Unavailable');
header('Retry-After: 3600');

Zend Framework

A závěrem ještě příklad, pro odeslání statusu 503 v Zend Frameworku:

$this->getResponse()->clearBody();
$this->getResponse()->setRawHeader('HTTP/1.1 503 Service Temporarily Unavailable');
$this->getResponse()->setRawHeader('Status: 503 Service Temporarily Unavailable');
$this->getResponse()->setRawHeader('Retry-After: 3600');

$msg = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">';
$msg.= '<html><head><title>503 Service Temporarily Unavailable</title>';
$msg.= '</head><body><h1>Service Temporarily Unavailable</h1>';
$msg.= '<p>The server is temporarily unable to service your request due to maintenance downtime or
capacity problems. Please try again later.</p>';
$msg.= '</body></html>';			

$this->getResponse()->setBody($msg);
$this->getResponse()->sendResponse();

Nov 19 2008

Povinné parametry funkcí v PHP

Tag: PHPJens @ 00:20

Vždycky jsem si myslel, že pokud není u funkce v PHP definován parametr jeho defaultní hodnotou — function Foo($a = ''){} — bere se, že je parametr povinný. Moje chyba, PHP mě opět vypeklo a přitom v tak elementární věci.

Mějme příklad:

<?php
function Bar($stuf)
{
	echo $stuf;
}
Bar();

výsledek:

Warning: Missing argument 1 for Bar(), called in /var/www/error.php on line 6 and defined in /var/www/error.php on line 2
Notice: Undefined variable: stuf in /var/www/error.php on line 4

Výstup mě nemile překvapil, nejen že volání funkce nepadne na Fatal Error ale navíc se tělo funkce provede s nedefinovanou hodnotou proměnné $stuf. Asi bude třeba na každý parametr volat isset(...). Jak geniální. Zdá se že povinné parametry v PHP neexistují.

Myslím že krása PHP spočívá právě v jeho nevyzpytatelnosti ;) Takže zkusme ještě podobný příklad, s určením typu „nepovinného“ parametru:

<?php
function Bar(array $stuf)
{
	echo $stuf;
}
Bar();

Výsledek je pak zcela logicky a jasně odvoditelný (!?!):

Catchable fatal error: Argument 1 passed to Bar() must be an array, none given, called in /var/www/error.php on line 6 and defined in /var/www/error.php on line 2

A proto v PHP rád programuji … spousty let a stále se učím základům :))
Vyzkoušeno na PHP 5.2.6.


Mar 31 2008

Zend Framework: Routování subdomén

Tag: PHP,SEO,Zend FrameworkJens @ 00:00

Routování v Zend Frameworku (ZF) není nic zvláště složitého. Stačí pouze lehce nahlédnout do dokumentance a začít používat defaultní routovaní případně si nastavit nějaká jiná vlastní pravidla.

Problém ale nastává v případě, že chcete do routování zahrnout subdomény. Na to ZF není přímo (zatím? — v době psaní tohoto článku je stable verze 1.5) vybaven a je tedy třeba použít kousek „vlastního kódu“.

Pokračování článku…


Mar 22 2008

Neuronová síť Backpropagation v PHP

Tag: Nezařazené,PHPJens @ 23:00

Jedná se o velmi triviální příklad dopředné neuronové sítě se třemi neurony a třemi vrstvami (vstupní vrstva, jedna skrytá vrstva a výstupní vrstva) a sigmoidou jako aktivační funkcí. Síť se dvěma vstupy a jedním výstupem lze naučit jednoduché logické funkce (např. XOR).

Pokračování článku…


Mar 03 2008

PHP hádanka – isset()

Tag: PHPJens @ 16:30

Co vypíše následující kód?

<?php

  $klic = 'klic';
  $arr = array($klic => 'hodnota');

  var_dump(isset($arr[$klic]['cokoliv']));
?>

Pokračování článku…


Feb 14 2008

Instalace eAcceleratoru na Debianu

Tag: Debian,Linux,PHPJens @ 23:00

eAccelerator je velmi užitečný nástroj pro „zrychlení“ PHP. Funguje na principu vytváření mezipaměti (cache) PHP skriptů a v případě že máte na serveru slabší stroj, nebo prostě jen potřebujete maximálně zrychlit vaše stánky je vhodné uvažovat o jeho použití. Jeho výhody se ukáží především při programování projektů založených na mnoha knihovnách, které se v průběhu vývoje nemění nebo pouze minimálně. eAccelerator vytvoří cache všech těchto PHP skriptů a není tedy nutné je při každém načtení stránky znovu kompilovat.

Pokračování článku…