PHP

Přehnaně Hodný Preprocesor

Pátek, 29. únor 2008

70 řádkové Smarty – nač dÄ›lat ze Å¡ablon vÄ›du?

Šablony jsou běžnou součástí dynamických webů. Není proto divu, že dosud vzniklo nemalé množství šablonovacích systémů, více či méně vydařených. Smarty osobně řadím do té druhé skupiny, myslím, že se jedná o nepraktický molouch spatlaných regulárních výrazů. Místo dalšího hanobení Smarty zneužiji jeho nestydaté velikosti a dám ji do kontrastu se svým skromným šablonovacím uzlíčkem o cca 70 řádcích.

Žádná odlehčená syntaxe, nic se nekompiluje, šablonu představují běžné PHP soubory, které bych se koderovi bez znalosti PHP zdráhal svěřit. Nezní to příliš přesvědčivě, že? Přinejmenším pro vlastní projekty je to ale báječná věc. Ono to totiž má i výhody.

Pořád ještě ho chcete vidět?

Ten šablonovací skript jestli chcete vidět. Ano? Tak tady je (pro PHP5).

A tuhle je ukázka, jak se s ním bezva pracuje:

require_once('Template.inc.php');

$doc = new Template('tpl/'); // Å¡ablony jsou v adresáři tpl/
$doc->nazev 'Hudební nástroje'// nastavíme nadpis stránky

$nastroje[] = array('nazev'=>'Harmonika''ladeni'=>'C#');
$nastroje[] = array('nazev'=>'Příčná flétna''ladeni'=>'F');
// zparsujeme seznam nástrojů
foreach($nastroje as $nastroj) {
    
$doc->parse('nastroj.php''.NASTROJE'$nastroj);
}
// tisknout Å¡ablonu stranka.php
$doc->parse('stranka.php'Template::dstPrint);

Neboli:

  • Vytvoříme instanci $doc. Potom můžeme nastavit její vlastnosti – říkejme jim z pohledu Å¡ablony globální promÄ›nné, i když tento termín má obecnÄ› jiný význam. Globální proto, že jsou přístupné ve vÅ¡ech Å¡ablonách parsovaných tímto objektem.
  • Následuje jednoduchý seznam hudebních nástrojů reprezentovaný asociativními poli. Ta dále použijeme pÅ™i parsování jako lokální promÄ›nné. Lokální proto, že budou dostupné jen v dané Å¡ablonÄ› a taky že mají pÅ™ednost pÅ™ed globálními.
  • Cyklem foreach dvakrát zparsujeme Å¡ablonu tpl/nastroj.php. Výsledek se pÅ™ipojí do promÄ›nné $doc->NASTROJE (PÅ™ipojí se díky té tečce na začátku, kdyby tam nebyla, tak tu promÄ›nnou pÅ™epíšeme.)
  • Nakonec parsujeme Å¡ablonu tpl/stranka.php, pÅ™ičemž ji pomocí speciální konstanty Template::dstPrint namísto uložení tiskneme. NÄ›kdy se může hodit Template::dstReturn, čímž, jak jistÄ› tušíte, obdržíme výsledek návratovou hodnotou.

Å ablona tpl/stranka.php

<html>
<head>
    <title><?=htmlspecialchars($nazev)?></title>
</head>
<body>
<h1><?=htmlspecialchars($nazev)?></h1>
<ul>
<?=$NASTROJE?>
</ul>
</body>
</html>

Å ablona tpl/nastroj.php

<li><strong><?=htmlspecialchars($nazev)?></strong> - ladÄ›ní <?=$ladeni?></li>

V obou šablonách jsem záměrně použil stejnou proměnnou $nazev, aby bylo vidět, že lokální proměnné mají přednost.

Příklad: Parsování vrácených řádků MySQL

$res mysql_query('SELECT jmeno, prijmeni FROM uzivatele');

while(
$row mysql_fetch_assoc($res)) {
    
$doc->parse('uzivatel.php''.UZIVATELE'$row);
}

Metody init(), check() a clear()

Metodu Template::init() voláme na začátku šablony, když je třeba určit, které rozšiřující funkce šablona používá (třeba pro práci s URL). Nedělá nic jiného než require_once všech skriptů, které jsou uvedeny jako parametry. Hledá je ve stejném adresáři, kde je umístěna třída a na konec automaticky doplňuje koncovku .inc.php. Je-li např. třída umístěna v adresáři lib/, pak Template::init('string')
provede require_once('lib/string.inc.php')

Template->check() ověřuje dostupnost šablony. Vrací TRUE pokud daná šablona existuje a lze ji přečíst.

Template->clear() čistí všechny nastavené vlastnosti objektu.

A ty výhody?

Zpracování Å¡ablon je pekelnÄ› rychlé (hlavnÄ› proto, že se nemusejí kompilovat). Dále, spousta Å¡ablonovacích systémů používá k nastavení promÄ›nných nÄ›jakou funkci, nejčastÄ›ji assign(). Nemohou tak dÄ›lat psí kusy jako tÅ™eba list($doc->id$doc->nazev) = mysql_fetch_row($res), což tady možné je.

Zde popsaný bastl nepovažuji za průlomové dílo, ostatně pár hodně podobných jsem už viděl. Na svou jednoduchost mi ale přijde nesmírně efektivní a jistě bude pro někoho alespoň inspirací.

V rubrice PHP, 00.33 | Trvalý odkaz | Komentáře (635)

Sobota, 19. leden 2008

Převracení boolean sloupce v administraci

U administrace je třeba ve většině případů počítat s tím, že ji může používat i více uživatelů naráz. Je zřejmé, že provedou-li změnu stejné databázové buňky současně, poslední zápis přepisuje ostatní. O tomhle problému se v souvislosti s PHP rozepsal Jakub Vrána.

Jsou ale situace, kdy pravidlo poslední vyhrává je tím nejmenším zlem. Jednou takovou je triviální převrácení boolean sloupce. Mějme například výpis článků u nichž je tlačítko publikovat, které se kliknutím přepíná na nepublikovat a zase zpět. URL takového tlačítka může vypadat takto:

  1. ?clanek=70&akce=prohodPublik
  2. ?clanek=70&akce=publikovat pro nepublikovaný článek
    ?clanek=70&akce=nepublikovat pro publikovaný článek

První zápis je jednoduchý a univerzální, protože ve výpisu měníme jenom ID článku a akce zůstává stejná. Převrátit boolean sloupec v databázi je také snadné. Přesto takové řešení nelze doporučit, protože převracejí-li uživatelé A a B hodnotu současně, vše se takhle zamotá:

  1. A i B vidějí článek nepublikováný
  2. A chce článek publikovat a prohodí jeho stav
  3. článek je nyní publikovaný, B který neobnovil stránku ho vidí jako nepublikovaný
  4. B chce článek publikovat a prohodí jeho stav
  5. článek je nyní nepublikovaný, což ani jeden z uživatelů nechtěl, navíc A ho vidí jako publikovaný

Druhý zápis si ve stejné situaci vede znamenitě. Uplatňuje ono pravidlo poslední vyhrává:

  1. A i B vidějí článek nepublikováný
  2. A chce článek publikovat a nastaví ho jako publikovaný
  3. článek je nyní publikovaný, B který neobnovil stránku ho vidí jako nepublikovaný
  4. B chce článek publikovat a nastaví ho jako publikovaný
  5. článek je nyní publikovaný, A i B ho vidějí správně
V rubrice PHP, 11.29 | Trvalý odkaz | Komentáře (6)

Pátek, 18. leden 2008

Vypnutí PHP v určitém adresáři

Neboli PHP, dost! Možná to bude pro někoho novinka, ale PHP se dá taky vypnout. V takovém případě server odešle skripty tak jak leží a běží. Jde mimochodem o skvělý způsob, jak zajistit bezpečnost při uploadování souborů formulářem. Doteď, když jsme se chtěli vyhnout spuštění cizího PHP skriptu na serveru, museli jsme (někteří) precizně kontrolovat koncovku či vyhodnocovat obsah. Ale ono stačí udělat v daném adresáři soubor .htaccess obsahující:

php_flag engine off

Všechny PHP skripty kolem takového .htaccess (včetně podadresářů) ztrácejí na své skriptovitosti. Stávají se z nich obyčejné textové soubory které nikomu ani v nejmenším nevadí. Tři věci je třeba mít na zřeteli:

  1. nemusí to fungovat na každém serveru (ani v takovém případě ovšem nehrozí riziko spuštění cizího PHP skriptu, protože server odmítne poslušnost se slovy Internal Server Error)
  2. pozor, aÅ¥ uživatel neuploaduje vlastní .htaccess namísto původního… to by byl docela trapas :-)
  3. server může obsluhovat i jiné skriptovací jazyky, např. Python či Ruby
V rubrice PHP, 16.23 | Trvalý odkaz | Komentáře (3)

Úterý, 8. leden 2008

Gradient v CSS za pomoci PHP a mod_rewrite

To jsem jednou potřeboval udělat barevný přechod. Ale ne GIMPárnu na jedno použití, nóbrž přechod dynamický, něco jako že by se zavolal soubor a vono se to vygenerovalo a tvářilo jako obrázek. S PHP a knihovnou GD je věc snadná a přidá-li se i mod_rewrite, pak i velmi slušivá. Laťku jsem si nastavil zhruba do téhle výše:

  1. možnost prolnutí více než dvou barev v libovolných vzdálenostech
  2. výběr mezi vertikální a horizontální orientací
  3. ukládání obrázku do cache
  4. bezpečnost z hlediska zatížení serveru či přeplnění cache

Co se týče bodu 1, skript dostane seznam klíčových barev a velikosti mezer které má mezi nimi proložit pÅ™echodem. Jako oddÄ›lovač takového seznamu jsem zvolil znak + který asi nejlépe znázorňuje spojování. VaÅ¡e komentáře mÄ› pÅ™imÄ›ly zmÄ›nit oddÄ›lovač na spojovník "-" Klíčové barvy se zapisují Å¡esti velkými hexadecimálními číslicemi, mezery desítkovÄ›. Aby se mezera odliÅ¡ila od barvy, může mít maximálnÄ› pÄ›t cifer – v praxi se použijí nanejvýš čtyÅ™i. Zbývá jeÅ¡tÄ› rozliÅ¡it orientaci (bod 2). Tak co tÅ™eba jedním malým písmenem na začátku? A bude to buď x pro horizontálnÄ› se opakující pÅ™echod nebo y pro pÅ™echod s opakováním vertikálním.

Bod 3: Skript na konci, pokud je to možné, uloží výsledek do souboru. Název souboru má stejný tvar s jakým pracuje mod_rewrite, takže pÅ™i příštím požadavku už soubor fyzicky existuje a má pÅ™ednost. Když vím, že obrázek potÅ™ebuju jenom jednou a je zbytečné ho cachovat (tÅ™eba jako v testovacím rámečku níž), pÅ™idám na konec parametr ?tmp. Jelikož parametry nejsou součástí názvu souboru, mají cachované soubory opÄ›t pÅ™ednost. A proč je to cachování tak nechutnÄ› důmyslné? Protože pÅ™episování URL je nastaveno case-sensitive, takže v cache nevzniknou duplicity – každý pÅ™echod je jednoznačnÄ› identifikován. Nuže a bod 4 vyÅ™ešíme omezením délky celého pÅ™echodu – dejme tomu na 5000px?

Zápis gradientu

Jak je popsáno výše, na začátku musí být malé písmeno x/y, za nímž následují klíčové barvy a číselně vyjádřené mezery mezi nimi. Pokud mezi barvami prolnutí být nemá (tedy nulová mezera), pak se zapíše ihned další barva, např. yFF00FF-00FF00 jsou fialový a zelený pixel vedle sebe. Jednoduchý gradient pro svislé opakování (y) získáme třeba pod tímhle názvem:

Způsob zápisu gradientu

Délku celého pruhu spočítáme součtem klíčových barev (3) a mezer mezi nimi (5+2). Pruh je tedy dlouhý 10px.

Jak se to teda používá

  1. adresář gradient/ si zkopírujete kam je pohodlno
  2. má-li fungovat cachování, je třeba povolit do něj zápis
  3. teď už v adresáři najdete jakýkoli obrázek gradientu, např. gradient/y00FF00-300-0000FF.png je 302px dlouhý přechod od zelené k modré

Kdo náhodou nemá k dispozici mod_rewrite, může skript volat přímo: gradient/gradient.php?repeat=y&grad=00FF00-300-0000FF

A proč se pruh roztahuje na 5px

… když by stačil 1px? Tučňákům nestačí. A možná i tygrům, pÅ™iznám se, že jsem po tom příliÅ¡ nepátral. Problém je v hladkosti pÅ™echodu. Zatímco pod Windows je opakovaný pÅ™echod stejný pÅ™i jakékoli šířce pruhu, FF/Linux ukáže 1px proužek pÅ™i nižší barevné hloubce jako nekoukatelnÄ› ohyzdné čáry. ZjevnÄ› nejprve obrázek proloží a teprve potom ho opakuje. Je tedy jasné, že jednorozmÄ›rný pÅ™echod nemůže proložit nikterak rozmanitÄ›, ovÅ¡em dostane-li vÄ›tší plochu, stane se s ním pÅ™ibližnÄ› tohle:

Opakovaný černobílý přechod při 8-bitové hloubce (FF/Linux)
Opakovaný černobílý přechod při 8-bitové hloubce (FF/Linux)

K pÄ›ti pixelům jsem doÅ¡el po sáhodlouhém zkouÅ¡ení. VÅ¡e do 3px je bída, 4px začínají být pÅ™ijatelné a pÄ›t – bingo! Vyšší hodnoty samosebou pÅ™inášejí výsledek o Å¡petku lepší, ale to už je příliÅ¡ na úkor velikosti souboru (ačkoli nijak drasticky, PNG komprese se celkem vytáhla). A protože tÄ›ch 5px je krásná a vždy použitelná hodnota, dal jsem ji na pevno do hlavičky skriptu namísto do URL, které je tím jednodušší. PÅ™ipomínám, že fujtajblproužkový problém se týká jen nížších barevných hloubek (do 16 bitů).

Zkouška

gradient/.png

Předvolby: B/W B/W/B R/G/B Černý panel Aqua moje :-)

A proč je v nadpisu "CSS"?

body {
  background:url('gradient/x664477-600-CC99FF.png') left top repeat-x fixed #CC99FF;
}

Download

gradient.zip

V rubrice PHP, 02.47 | Trvalý odkaz | Komentáře (10)

Pátek, 27. červenec 2007

WebZdarma a očůrání reklamy

Teda, ne úplně, protože jak administrátoři WebZdarma tvrdí, skryjete-li reklamu, máte po ptákách.

Chci se zaměřit na následující situaci:

  • Jste donuceni snížit se k umístÄ›ní PHP stránek na WebZdarma
  • PotÅ™ebujete do skriptů vkládat tÅ™eba soubor config.php s citlivými údaji, jako je login k MySQL apod.

Nejprve něco k bezpečnosti. Nejlepší řešení je samozřejmě umístit citlivé soubory mimo DocumentRoot, to ale na WZ nejde. Je tedy nanejvýš vhodné, aby config.php měl koncovku .php, protože koncovka jiná, třeba .inc, by stačila k tomu, aby kdokoli zadal do prohlížeče http://superstranky.wz.cz/config.inc a hesla viděl v celé jejich kráse (server nepozná, že soubor má prohnat skrz PHP a běžně ho odešle). Pro všechny případy není od věci takovému souboru ještě zakázat přístup zvenčí pomocí .htaccess. Jistota je jistota.

V čem je problém? Protože jeden podobný web spravuji, vÅ¡iml jsem si, že WebZdarma Å™eší vkládání reklamy pÅ™i uploadu PHP souboru – prostÄ› ji prasácky mrskne na konec (je to potom rychlé). Když do skriptu includujete 10 .php souborů a máte nastaveno zobrazování reklamy na stránce dole, uvidíte ve výsledku celkem 11 reklam na jediné stránce. SamozÅ™ejmÄ›, každý soubor obsahoval reklamu. Jedno z Å™eÅ¡ení je použít funkce na řízení výstupu:

<?php
ob_start
(); require_once('config.php'); ob_end_clean();
?>

Ale s mírou, protože tahle metoda zhltne veškerý výstup skriptu, včetně případných chyb. Ostatním vkládaným souborům které nemusejí být nutně skryté je lepší dát koncovku .lib či podobně. Tím se zamezí automatickému připojení reklamy, pročež není třeba je dál ošetřovat.

Tak, a teď ještě jedna možnost. Stačí dát na konec každého includovaného souboru return;, čímž se vkládání ukončí. Dejte ale pozor, aby se jednalo skutečně o vkládané soubory, protože pokud půjde o opravdickou stránku, nezobrazí se reklama ani na ní a vystavujete se riziku, že vám web smažou. Sice nemohu ručit za to, že se to v ostatních případech nestane, ale podle mě je tohle naprosto korektní ošetření závažného nedostatku. Cílem přece je, aby se na stránce zobrazovala právě jedna reklama.

Aktualizace

Jiří Počta poznamenává, že stačí zvolit v administraci ruční vkládání reklamy.

V rubrice PHP, 11.00 | Trvalý odkaz | Komentáře (7)

Úterý, 10. červenec 2007

mysqldump pro PHP

V knihovnÄ› mysqldump.php [ZIP] najdete dvÄ› funkce:

  • fmysqldump() – zapisování MySQL dumpu do souboru
  • mysqldump() – tisk MySQL dumpu na výstup

Syntax

bool fmysqldump ( resource $fp [, resource $link [, mixed $data_for [, mixed $struct_for]]] )

mixed mysqldump ( [ resource $link [, mixed $data_for [, mixed $struct_for [, bool $return ]]]] )

$fpukazatel na otevřený soubor, do něhož se má dump zapisovat.
$linkotevřené MySQL spojení s vybranou databází. Pokud není uvedeno nebo je NULL, použije se poslední spojení.
$data_forpole s názvy tabulek, jejichž data mají být dumpována. Např. array('tabulka1', 'tabulka2') Hodnota TRUE je výchozí a znamená všechny tabulky, array() pak znamená žádné tabulky.
$struct_forpole s názvy tabulek, jejichž struktura má být dumpována. Hodnota TRUE je výchozí a znamená všechny tabulky, array() pak znamená žádné tabulky.
$returnnamísto tisku na výstup vracet dump návratovou hodnotou. Pozor na přetečení paměti, dumpy bývají veliké.

Paměť

Teoreticky by naráz nemělo být obsazeno o moc víc paměti než je velikost největšího sloupce v dumpovaných tabulkách, ale ve skutečnosti jsem pro tabulky s plně obsazenými sloupci MEDIUMBLOB potřeboval povolit 30 MiB, zřejmě hlavně proto, že některé binární znaky se do mezipaměti ukládají escapované.

Použití v praxi

<?php
require_once('mysqldump.php');

# pÅ™ipojit MySQL
mysql_connect('hostitel''uzivatel''heslo');
mysql_select_db('moje_databaze');

mysql_query('SET CHARACTER SET utf8');

# budeme posílat SQL soubor "zaloha_moji_databaze.sql"
# a chceme prohližeč rovnou vyzvat k uložení (attachment)
header('Content-Type: text/x-sql');
header('Content-Disposition: attachment; filename="zaloha_moji_databaze.sql"');
# neukládat data do cache
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Thu, 30 Jun 1988 12:00:00 +0000 GMT');

# pro MEDIUMBLOB/MEDIUMTEXT sloupce je tÅ™eba navýšit paměť
ini_set('memory_limit''32M');

# poslat dump na výstup
mysqldump();
?>

Tenhle skript umožní klientovi stáhnout přes prohlížeč dump všech tabulek v otevřené databázi.

Pozn.: Knihovna mysqldump je šířena v naději, že bude užitečná, avšak bez jakékoli záruky.

V rubrice PHP, 03.17 | Trvalý odkaz | Komentáře (164)

Pondělí, 2. červenec 2007

PHP trik týdne - include() řetězce

<?php
$str 
'Ahoj <?php echo "svÄ›te!" ?>';
eval(
'?>'.$str.'<?php ');
?>

Normálně se eval() chová tak, že očekává PHP kód. Vtip je v tom, že ten se dá běžným způsobem uzavřít a na konci znovu otevřít, čímž uprostřed získáváme prostor pro HTML či jiný čistý text. A konečně, ten může znovu obsahovat PHP vsuvky, zkrátka se proměnná zpracuje zrovinka jako by se includoval soubor. Pokud vám zmíněný trik fungovat nebude, budu vděčný, když se v komentářích ozvete. Rád bych si udělal přehled o kompatibilitě. Já testoval na PHP 4.3.10-16 a 4.4.4.

V rubrice PHP, 21.02 | Trvalý odkaz | Komentáře (14)

Pátek, 2. březen 2007

Tempie zveřejněna

Tempie jest GNU šablonový engine pro PHP. Odedneška jej naleznete na http://tempie.org.

V rubrice PHP, 23.10 | Trvalý odkaz | Komentáře (13)

Neděle, 22. říjen 2006

Error Handling in PHP

At first, please overlook some language mistakes in the text, for although I do my best, this is the first time I have condescended to write an English article.

High-quality error logging is a powerful tool for debugging any sort of applications, even your PHP scripts. It gives you a control of their behavior, so if you provide a complex application, e.g. firm database, you can reveal errors rised during the live traffic. I want to show you my solution, consisting of one PHP class and additional MIME function. We'll be able to log errors and warnings to a file, send them via e-mail, and display them in cool colours as well.

Our class will be called by the trigger_error function. This is a standard PHP function as of PHP 4.0.1 and allows you to display a user-defined error message. It's possible to handle its calls, so first thing we have to do after the class setup is to call the set_error_handler function. Until PHP 5, it takes only one parameter, which must be a valid callback of user handling function. The handling function needs to accept two parameters - error number and error string. Additional three parameters may be supplied - the file where error occured, the line number, and the error context (array of variables present in the scope where error occured).

One important fact is that our function is not affected by the error_reporting settings and will be called every time the error occurs. We have to attend this selves inside the handling function. As of PHP 5, there is a optional parameter error_types directly in set_error_handler function.

We should also realize that the trigger_error outputs an error message even after setting the error handler function. Therefore, it's necessary to turn error_reporting off. Our function will still be called (see above). In practice, the only errors we'll be able to catch are E_WARNING, E_NOTICE, E_USER_ERROR, E_USER_WARNING and E_USER_NOTICE. When another error occurs (fatal, parsing, etc.), script won't be executed, not either set_error_handler function.

Finally, obvious facility is that if you throw up the class and set_error_handler function, application will still work properly, just depleted of error logging.

Let's see the code.

<?php
/*
* Error handling class
* --------------------
*
* @author     Honza Odvárko <honza@odvarko.cz>
* @copyright  Copyright 2006 Honza Odvárko
* @license    GNU GENERAL PUBLIC LICENSE
*
* Thanks to http://www.faqs.org/rfcs/rfc2047.html
*/

# Outputs RFC 2047 encoded-word
# $encoding_method must be either 'b' (Base64 encoded) or 'q' (Quoted-Printable)
# $charset parameter only describes input encoding, there is no conversion involved
#
function mime_encoded_word($str, $encoding_method=null, $charset=null) {
    if(
"$encoding_method" === '') $encoding_method = 'q';
    if(
"$charset" === '') $charset = 'utf-8';
    switch(
strtolower($encoding_method)) {
    case
'b':
        
$buf = "=?$charset$?B?".base64_encode($str).'?=';
        break;
    case
'q':
        
$buf = "=?$charset?Q?";
        for(
$i=0,$c=strlen($str); $i<$c; $i++) {
            
$ch = $str{$i};
            
$ord = ord($ch);
            if(
$ch == ' ') {
                
$buf .= '_';
            } elseif(
$ord > 31 && $ord < 127 && $ch != '=' && $ch != '?' && $ch != '_') {
                
$buf .= $ch;
            } else {
                
$buf .= '='.strtoupper(dechex($ord));
            }
        }
        
$buf .= '?=';
        break;
    default:
        
trigger_error("Invalid encoding method '$encoding_method' (must be either 'b' or 'q')", E_USER_WARNING);
        return
false;
    }
    return
$buf;
}

class
err {

    
# describes encoding of error string
    
var $encoding = 'utf-8';

    
# which errors will be displayed
    
var $reporting = E_ALL;

    
# which errors will be logged to a file
    
var $logfile_level = E_ALL;

    
# log file location
    
var $logfile;

    
# which errors will be sent via e-mail
    
var $logmail_level = E_ALL;

    
# comma-separated list of log-mail recipients
    
var $logmail;


    function
set_encoding($value) {
        
$this->encoding = $value;
    }

    function
set_reporting($value) {
        
$this->reporting = $value;
    }

    function
set_logfile_level($value) {
        
$this->logfile_level = $value;
    }

    function
set_logfile($value) {
        
$this->logfile = $value;
    }

    function
set_logmail_level($value) {
        
$this->logmail_level = $value;
    }

    function
set_logmail($value) {
        
$this->logmail = $value;
    }


    function
handler($errno, $errstr, $errfile, $errline, $errcontext) {

        
$use_reporting = 0!=($this->reporting     & $errno);
        
$use_logfile   = 0!=($this->logfile_level & $errno);
        
$use_logmail   = 0!=($this->logmail_level & $errno);

        switch(
$errno) {
        case
E_WARNING:      $errtype='Warning'; $errcolor='orangered'; break;
        case
E_NOTICE:       $errtype='Notice';  $errcolor='green'; break;
        case
E_USER_ERROR:   $errtype='Error';   $errcolor='red'; break;
        case
E_USER_WARNING: $errtype='Warning'; $errcolor='orangered'; break;
        case
E_USER_NOTICE:  $errtype='Notice';  $errcolor='green'; break;
        default:
            
$errtype='Unknown error';
            
$errcolor='red';
        }

        if(
$use_reporting) {
            
header('Content-Type: text/html; charset='.$this->encoding);
            echo
"<br />\n<b style=\"color:$errcolor\">$errtype</b>: ".htmlspecialchars($errstr).
                
' in <b>'.htmlspecialchars($errfile)."</b> on line <b>$errline</b><br />\n";
        }

        if(!
$use_logfile && !$use_logmail) return true;

        
$date = date('r');
        
$dump = wddx_serialize_vars('errcontext');
        
$dumpsize = strlen($dump);
        
$record =
            
"ERROR\n".
            
"    no: $errno\n".
            
"  type: $errtype\n".
            
"  text: $errstr\n".
            
"  file: $errfile\n".
            
"  line: $errline\n".
            
"  date: $date\n".
            
"\n".
            
"SERVER\n".
            
"  http_host:       {$_SERVER['HTTP_HOST']}\n".
            
"  http_user_agent: {$_SERVER['HTTP_USER_AGENT']}\n".
            
"  remote_addr:     {$_SERVER['REMOTE_ADDR']}\n".
            
"  script_filename: {$_SERVER['SCRIPT_FILENAME']}\n".
            
"  request_method:  {$_SERVER['REQUEST_METHOD']}\n".
            
"  request_uri:     {$_SERVER['REQUEST_URI']}\n".
            
"\n".
            
"CONTEXT DUMP [$dumpsize B]\n".
            
$dump;

        
$succeed = true;

        if(
$use_logfile) {
            if((string)
$this->logfile !== '') {
                if((
$fp = fopen($this->logfile, 'a')) !== false) {
                    
$write =
                        
"\n\n\n--------------------------------------------------------------------------------\n".
                        
$record;
                    
fwrite($fp, $write);
                    
fclose($fp);
                } else {
                    
$succeed = false;
                    
$this->_internal_warning("Unable to open log file '{$this->logfile}' for writing", __FILE__, __LINE__);
                }
            } else {
                
$succeed = false;
                
$this->_internal_warning('Log file is not specified', __FILE__, __LINE__);
            }
        }

        if(
$use_logmail) {
            if((string)
$this->logmail !== '') {
                
$to = $this->logmail;
                
$subject = "$errtype: $errstr";
                
$subject = mime_encoded_word($subject, null, $this->encoding);
                
$message = $record;
                
$message = str_replace("\n.", "\n..", $message); # SMTP workaround (Windows only)
                
$headers[] = 'Mime-Version: 1.0';
                
$headers[] = 'From: ERROR-HANDLER';
                
$headers[] = 'Content-Type: text/plain; charset='.$this->encoding;
                
$headers[] = 'Content-Transfer-Encoding: 8bit';
                
$headers[] = 'X-Mailer: PHP/'.phpversion();
                if(!
mail($to, $subject, $message, implode("\r\n", $headers))) {
                    
$succeed = false;
                    
$this->_internal_warning('Unable to send log mail', __FILE__, __LINE__);
                }
            } else {
                
$succeed = false;
                
$this->_internal_warning('Log mail recipients are not specified', __FILE__, __LINE__);
            }
        }

        return
$succeed;
    }


    function
_internal_warning($errstr, $errfile, $errline) {
        
header('Content-Type: text/html; charset=utf-8');
        echo
"<br />\n<b style=\"color:orangered\">Error handler warning</b>: ".htmlspecialchars($errstr).
            
' in <b>'.htmlspecialchars($errfile)."</b> on line <b>$errline</b><br />\n";
    }
}
?>

Sample usage

<?php
require_once('err.class.php');

$err = new err();

$err->set_reporting    (E_ALL & ~E_NOTICE); # display all except notices
$err->set_logfile_level(E_ALL); # log all (default, may be omitted)
$err->set_logfile      ('error_log.txt');
$err->set_logmail_level(E_ALL & ~E_NOTICE); # mail all except notices
$err->set_logmail      ('webmaster@example.org');

# let the handling function take control
set_error_handler(array($err, 'handler'));

# hide errors which we can handle
error_reporting(E_ALL & ~E_WARNING & ~E_NOTICE & ~E_USER_ERROR & ~E_USER_WARNING & ~E_USER_NOTICE);

# sample user warning
trigger_error('This is my own warning message', E_USER_WARNING);

# this produces warning too
implode();
?>

Don't forget to allow the write permissions to the directory where a log file should be created. Now if you see the orange warnings, they were successfuly handled and logged by the handling class.

Update

Thanks to Mrs. Matoušková for the semantic correction.

V rubrice PHP, 01.52 | Trvalý odkaz | Komentáře (384)

Úterý, 5. červenec 2005

PHP a MySQL: Obrázky v databázi

Aktualizace

Přesunul jsem svůj článek na server IT-MaX, aby tu nestrašil. Takhle rozsáhlé obludnosti už budu rovnou vystavovat na podobných serverech a tady na ně jen odkážu nějakým pěkným odkazem.

V rubrice PHP, 20.06 | Trvalý odkaz | Komentáře (3)

Rubriky

Navigace

Hledání