Tipy k používání Mac OS X

Podobné články obvykle začínají slovy „zapomeňte vše, na co jste zvyklí z Windows“. Je nesmysl po uživatelích něco takového žádat, protože návyků získaných za mnoho let používání počítače se nijak snadno zbavit nejde.

Namísto toho se s vámi chci podělit o tipy a zkušenosti, které jsem získal za 2 měsíce používání Macbooku. Se spoustou věcí jsem bojoval, narážel na slepé uličky, propadal zoufalství. V poslední době mám ale pocit, že se situace obrací a začínám poznávat výhody Mac OS X oproti PC.

Pravidlo č. 1 - zapomeňte na semafor! Trojice barevných tlačítek v levém horním rohu okna každému připomene podobnou trojici z Windows, ale jejich funkce se liší. Zelené + většinou okno nemaximalizuje - namísto toho změní velikost okna na tu, o které si myslí, že je ideální. Mezi žlutým a červeným tlačítkem prakticky není rozdíl. Obě de facto skrývají okno, jen stisk žlutého provází animace.

Namísto snahy strefit se do malých tlačítek je daleko pohodlnější používat klávesové zkratky. Cmd+W je nejčastější zkratka, kterou v současnosti používám. Je to ekvivalent stisku červeného tlačítka. Aplikace se nevypne a dál běží na pozadí, pouze se zavře její aktuální okno. Kompletně se dá aplikace vypnout přes Cmd+Q, ale zjistil jsem, že to dělám naprosto minimálně, spíš v nouzových situacích. Proti Cmd+Q hovoří i to, že se nedá použít ve Finderu.

OS X smazává rozdíl mezi vypnutými a běžícími aplikacemi. Abych tuto myšlenku podpořil, vypnul jsem si v nastavení zobrazování modrých teček pod ikonami v Docku. (System Preferences - Dock - Show indicator lights for open applications) Jednak mě iritovalo, že modrá tečka u aplikace zůstávala i po stisknutí červeného tlačítka a druhak mi to připomínalo indikátor nových příspěvků na Twitteru (myslel jsem tedy, že je v aplikaci „něco nového“, na co bych se měl podívat). Netuším tedy, jaké aplikace mi tu zrovna běží a jako uživatele mě to ani nezajímá.

Na Macbooku se nedá nastavit, co se má stát po zavření víka. Pokud není připojený k externímu monitoru a zároveň k napájení, uspí se. Pokud chcete skončit s prací nebo se přesunout, nemusíte v nastavení lovit nějaký režim spánku, stačí ho zaklapnout. Probuzení po otevření je téměř okamžité.

Doporučuji si projít System Preferences - Trackpad. Je tam seznam všech gest, které lze na touchpadu provádět a případně si je pozapínat/povy­pínat. Touchpad se dá stisknout kdekoli, lze tedy používat stejný prst pro přesouvání kurzoru i kliknutí. Překvapuje mě, kolik lidí tuhle zásadní věc neví.

Trochu divoce na mě působilo ovládání Finderu. Enter přejmenovává soubor, otevírá se pomocí Cmd+O. Zásadní vlastností je ale náhled, který se vyvolává mezerníkem. Zvládá mnoho typů souborů a neotevírá se kvůli němu specializovaná aplikace, jeho zobrazení je tudíž velice rychlé. Dokonce dokáže zobrazit i .docx, což v základu neumí ani Windows. O PDF nemluvě. Možnosti Finderu rozšiřuje TotalFinder.

Aplikace, které nejsou z App Store, se instalují prostým přetáhnutím souboru .app z archivu do adresáře Applications ve Finderu.

Kapitolou sama pro sebe je klávesnice. Oproti PC je zde jedna modifikátorová klávesa navíc, Cmd. Tam, kde se na PC používá Ctrl, zde Cmd. Pro práci s textem tedy slouží Cmd+X, Cmd+C, Cmd+V. Ctrl zde slouží jen jako náhrada v případech, kdy Cmd nelze použít. Hledání se ve všech aplikacích vyvojá přes Cmd+F, ale pokud jsem v textovém editoru a chci použít Najít+Nahradit, nemohu použít Cmd+H, protože tu má rezervovanou systém pro minimalizaci okna. Musím tedy stisknout Ctrl+H. Pokud chci přecházet v nějaké aplikaci mezi taby, nemohu použít Cmd+Tab, protože to má opět rezervované systém pro zobrazení seznamu oken. Opět zde zafunguje Ctrl+Tab. Takto se dá odvodit většina zkratek.

OS X i autoři aplikací význam klávesových zkratek dodržují. Ve všech funguje Cmd+čárka pro přechod do nastavení. Pokud v aplikaci existuje více různých pohledů, dá se mezi nimi přecházet přes Cmd+1, Cmd+2, … Stisk funkčních kláves F1 - F12 ve výchozím stavu provede to, co je na nich nakreslené, tedy ztmavení/zesvětlení displeje a klávesnice, přehrávání hudby apod. Skutečný stisk F1 - F12 se dá provést s modifikační klávesou Fn. Ale ještě jsem to nepotřeboval. Pro obnovení stránky, což při vývoji webů potřebuji velice často, se dá ve všech prohlížečich použít Cmd+R namísto F5, na které jsem byl zvyklý na Windows.

Na klávesnici chybí Home, End a Page Up/Down. Nahrazuje ho Cmd v kombinaci s kurzorovými šipkami. Stejně tak chybí Delete pro dopředné mazání textu, funguje ovšem Cmd+Backspace.

Dokud jsem nepotřeboval na Macbooku něco naprogramovat, vyhovovala mi výchozí česká klávesnice. Speciální znaky jsou na ni umistěny ovšem tak netradičně, že jsem lámání prstů po dvou dnech vzdal. Existuje nástroj Ukulele, který umožňuje nadefinovat si pohodlně vlastní rozložení klávesnice. Další možnosti skýtá „KeyRemap4Macbook“, který např. umožňuje z klávesy Eject udělat klasický Delete. Ale pozor, Airy už Eject nemají.

Konečně se dostávám k aplikacím. Nejdůležitější je stále webový prohlížeč, i když nemá zdaleka takový význam jako na Windows. Smysl má uvažovat pouze o Google Chrome nebo Safari. Mozilla Firefox si s Macem příliš nesedí. Pro Chrome hovoří jeho svižnost a neustále se zvětšující podíl na trhu, ze Safari je zas cítit lepší provázanost se systémem.

Webový prohlížeč na Macu ustupuje do pozadí, protože pro většinu webových aplikací jsou zde k dispozici jejich desktopové varianty, které se se svými webovými protějšky umí synchronizovat. Nativní aplikace na Macu jsou velice příjemné a pohodlnější, než jejich webové protějšky. Je přirozené, aby aplikace, se kterou stále pracuji, měla zvláštní okno na ploše, než jen jeden titěrný tab ve webovém prohlížeči. Zastávám teorii, že webové aplikace slaví takový úspěch jen kvůli tomu, že na Windows není nikdo schopný vytvořit funkční a pěknou aplikaci.

Pro uživatele Gmailu doporučuji Sparrow, klient s příjemným minimalistickým rozhraním. Podporuje všechny hlavní funkce Gmailu - štítky, hvězdičky, vyhledávání. To probíhá nikoli procházením veškeré pošty na disku, ale dotazováním se na servery Googlu, jako kdybyste použili vyhledávací pole. Odezva je tedy okamžitá. Vzpomínám si, když jsem před lety dal něco vyhledávat v Outlooku, mohl jsem si jít uvařit kafe, kdybych ho tehdy pil.

Pro uživatele Google Readeru je Reeder (existuje i pro iPhone a iPad). Taktéž se synchronizuje s webovým rozhraním, umí házet články do Read It Later a podporuje Readability (očesání webové stránky o zbytečnosti okolo pro pohodlné čtení).

Samozřejmostí je oficiální Twitter for Mac, který taktéž jde ve šlépějích svých protějšků pro iPhone a iPad.

Kalendáře z Google Calendaru jsem si naházel do předinstalovaného iCalu.

Protože jsem si na Windows vypěstoval oprávněnou nenávist vůči iTunes, ohlížel jsem se po alternativním přehrávači hudby. Ve stručnosti - neexistuje. Jako útěchu můžu říct to, že iTunes jsou na Macu docela jiný software. Dobře to popisuje moje příhoda, kdy jsem k Macbooku poprvé připojil svůj iPhone k synchronizaci a čekal celosystémový několikavteřinový lag jako na Windows. Ale nic se nedělo. Kontroloval jsem tedy iPhone, jestli je zapnutý, kontroloval jsem, jestli jsem správně zapojil kabel. Vše bylo v pořádku a přesto se nic nedělo. Otevřel jsem iTunes a ten mi signalizoval právě dokončenou zálohu iPhonu :) Dá se s nimi tedy vyjít.

Pro instant messaging se hojně používá Adium, to mi ale příliš do oka nepadlo. Existuje ovšem málo známý multiplatformní messenger od Mozilly, který se jmenuje Instantbird. Hodně mi připomíná Mirandu, na kterou nedám dopustit.

Na poznámky používám Evernote, který snad není třeba představovat.

Pro evidenci úkolů jsem si nedávno začal pohrávat s Things, který do puntíku dodržuje metodiku GTD. Představuje ovšem nemalou investici. Já využil toho, že nyní probíhá betatesting synchronizace do cloudu, takže zatím používám vývojovou verzi. Pokud se osvědčí, koupím si je.

Pokud často pracujete s příkazovou řádkou, doporučuji iTerm. Oproti výchozímu terminálu přináší značná zlepšení použitelnosti.

Příští neděli mířím na Appleforum, je tedy dost možné, že po něm budu muset tento článek z půlky přepsat, nebo celý zahodit.

Doctrine vs. NotORM vs. zbytek světa

Jakub Vrána na dnešním Barcampu ve své přednášce srovnával Doctrine 2 ORM se svou knihovnou NotORM. V jednoduchosti kódu, počtu řádků a vykonaných dotazů nutných pro vypsání článků a přiřazených tagů z databáze zvítězilo nepřekvapivě NotORM, což z něj u naivních programátorů hned dělá kandidáta na modelovou část webové aplikace. Z čehož mi přebíhá mráz po zádech.

Hlavní motivací pro používání Doctrine je konzistentní reprezentace dat v aplikaci a práce s nimi. A tu mi zařídí objekty. Nikoli obálky nad funkce (třida se samými statickými metodami), ani obálky nad data (třída s public proměnnými či obecným ukládacím mechanismem jako DibiRow), ale skutečné objekty reprezentující nějaký smysluplný celek s nenarušitelnou konzistencí a metodami, které mi umožní práci s nimi.

Pokud to vezmu z toho konce, že chci do databáze uložit nějaké řádky a pak je zase vybrat, pak mám s Doctrine skutečně o mnoho víc psaní, které nedává smysl. Ale pokud si primárně stanovím, že chci v aplikaci pohodlně pracovat s konzistentními objekty, jak se to standardně dělá třeba v Javě (a PHPčkařům to stále neleze pod kůži), pro ukládání do databáze pomocí Doctrine už musím udělat jen minimum - napsat ke třídě a jejím proměnným pár anotací.

Jakou máme motivaci pro tento způsob psaní aplikace a odstoupení od jednoduchých databázových vrstev, jako je právě NotORM nebo dibi? Ve chvíli, kdy velikost aplikace překročí určitou mez, je model s de facto statickými metodami, ve kterém se míchá přístup do databáze, zápis do souborů a posílání mailů, dlouhodobě neudržitelný chaos. Na řadu pak přichází dnes již legendární pětivrstvý model, jehož nejbližší implementací v PHP je právě Doctrine 2.

Tato architektura umožňuje snadnou testovatelnost a tudíž i spolehlivost, flexibilitu (API modelu a tudíž ani prezentační vrstva se při překopání databáze nemusí měnit), je dlouhodobě udržitelná a když přijde řeč na vícejazyčnost a verzování, lze to udělat čistě a bez vytrhání všech vlasů. V této oblasti se NotORM, ani klasické Nette modely, jak byly dlouhou dobu komunitou upřednostňovány, skutečně nechytají.

Samozřejmě to není jediný správný způsob, jak k problému přistoupit. Některé firmy/projekty volí přístup mít všechnu logiku v databázi. PHP aplikace je pak stavěná pouze do role jednoduché prezentační vrstvy a všechny operace se provádějí pomocí databázových procedur. Proč ne.

V Doctrine je určitě prostor pro optimalizaci pokládaných dotazů a umím si představit, že by k tomu použila NotORM. Ale mít tuto knihovnu jako jedinou vrstvu modelu u rozsáhlejších aplikací vyvíjenými více lidmi moc dobře nejde.

Testování není nástroj, ale metoda vývoje

Stále se ještě setkáváme s názory, že jednotkové testování je práce navíc a že není, kdo by ho zaplatil. Tento článek si klade za cíl poodhalit tuto techniku vývojářům a projektovým manažerům, kteří v ni nevěří, nebo neví, že dokáže výrazně usnadnit a zkvalitnit odvedenou práci.

Můj první článek na Zdrojáku.

PHP 5 OOP cheatsheet (tahák)

Zápis některých aspektů objektově orientovaného programování v PHP má poměrně velký WTF faktor a nejednou jsem narazil na pokročilého programátora, jak tápal, proč jeho kód způsobuje parse error či nějakou jinou chybu. Rozhodl jsem se proto oprášit můj blog a přehledně na jednom místě shrnout OOP syntax v PHP.

U jednotlivých popisů budu velice stručný, článek si neklade za cíl vysvětlit OOP, zaměřuje se pouze na způsob zápisu.

K hezkému objektovému kódu patří i jeho jednotná podoba, doporučuji k nastudování Nette Coding Standard.

Třídy

Definice nové třídy

//obecná třída
class A {

}

//abstraktní třída - nelze vytvořit instanci, to až u potomka
abstract class A {

}

//finální třída - nelze podědit
final class A {

}

//definice třídy B, která dědí od třídy A
class B extends A {

}

Konstruktor

Metoda, která se jmenuje __construct() a volá se při vytvoření instance nové třídy.

class A {
    public function __construct() {

    }
}

//konstruktor může mít parametry
class A {
    public function __construct($name) {

    }
}

Vytvoření instance

$a = new A; //pokud konstruktor nemá žádné povinné parametry
$a = new A(); //ekvivalentní zápis

$a = new A('foo'); //konstruktor s povinným parametrem

Rozhraní (interfaces)

//obecný interface
interface A {

}

//interface B, který dědí od interface A
interface B extends A {

}

//třída B, která implementuje interface A
class B implements A {

}

/* PHP umožňuje implementaci více rozhraní naráz */

//třída C, která implementuje interfaces A a B
class C implements A, B {

}

Viditelnost (zapouzdření)

U metod a statických/in­stančních členů (atributů třídy) lze nastavit viditelnost pro volání (u metod), resp. pro čtení a zápis (u atributů). PHP, stejně jako ostatní jazyky, podporuje tři typy viditelnosti:

  • public: volat metody a přistupovat k atributům může každý
  • protected: pouze samotná třída, její potomci (při překrývání - overriding - i předci) a sourozenci (dvě různé instance typu B či nadtypu A si navzájem mohou sahat na protected metody a atributy)
  • private: pouze samotná třída

Atributy

Definice atributů

Konvence říká, že všechny atributy by měly být definovány na začátku definice třídy.

class A {
    //instanční atributy
    var $a; //zastaralý zápis public
    public $b;
    protected $c;
    private $d;

    //statické atributy
    public static $e;

    //atributům lze předat výchozí hodnoty primitivních datových typů anebo pole
    //složitější data se musí předávat v konstruktoru
    private $f = '';
    private $g = 'foo';
    private $h = 108;
    private $i = array('bar');
}

Přistupování k atributům

$a = new A;

//zvenčí, k instančním
$a->a = 'foo';

//zvenčí, ke statickým
A::$e = 'bar';

//zevnitř třídy, k instančním
$this->c = 'foo';
echo $this->f;

//zevnitř třídy, ke statickým
self::$e = 'bar';

Konstanty

PHP umožňuje definovat neměnitelné atributy třídy, např. pro všelijaká nastavení a podporu principu DRY.

class A {

    //definice konstanty
    const DEFAULT_USER = 'foobar';

}

//přístup zvenčí
echo A::DEFAULT_USER;

//přístup zevnitř
echo self::DEFAULT_USER;

Metody

Definice metod

class A {

    //metody bez uvedení viditelnosti jsou brány jako public
    function methodFoo() {

    }

    public function methodBar() {

    }

    protected function methodFooBar() {

    }

    private function methodBarFoo() {

    }

    //metoda může být abstraktní, její definici pak necháváme na potomkovi třídy
    abstract public function methodAbstract();

    //metoda může být i finální, nelze ji pak v potomkovi překrýt
    final public function methodFinal() {

    }

}

Parametry metod

class A {

    //metoda, která přijímá dva parametry
    public function methodExample($a, $b) {

    }

    //PHP neumožňuje přetěžování metod (overloading), umí ale nepovinné parametry
    //metodu lze zavolat se dvěma nebo třemi parametry
    public function methodFoo($name, $age, $sex='male') {

    }

    //lze definovat, jakého typu má přijímaný parametr být
    //nejde použít pro primitivní datové typy - lze vynutit pouze pole, interface nebo třídu - funguje zde polymorfismus
    public function methodBar(array $names, Person $p, Traversable $t) {

    }

    //i metody mohou být statické
    public static function methodStatic() {

    }

}

Volání metod

$a = new A;

//instanční zvenčí
$a->methodExample('foo', 'bar');

//instanční zevnitř
$this->methodFoo('foo', 'bar');
$this->methodFoo('foo', 'bar', 'female');

//statické zvenčí
A::methodStatic();

//statické zevnitř
self::methodStatic();

Překrývání

Pokud v potomkovi definuji atribut nebo metodu s názvem, který existuje už v předkovi, a jejich viditelnost je public či protected, dojde k překrytí (overriding).

class A {
    public $foo = 'foo';

    public function methodExample() {
        return 'foo';
    }
}

class B extends A {
    public $foo = 'bar';

    public function methodExample() {
        return 'bar';

        //pomocí parent::methodExample() mohu zavolat metodu předka
    }
}

$b = new B;
echo $b->foo; //vypíše bar
echo $b->methodExample(); //vypíše bar

Pokud se v třídním předkovi odkazuji na public/protected atribut či metodu, která je v potomkovi přepsána, volá se/přistupuje se k implementaci potomka.

Pozor! Tento způsob nefunguje u statických atributů a metod, tam se při přístupu pomocí self:: bude stále volat implementace v předkovi. Tento nedostatek řeší late static binding v PHP 5.3, viz níže.

class A {
    protected $foo = 'foo';

    public function getFoo() {
        return $this->foo;
    }
}

class B extends A {
    protected $foo = 'bar';
}

$b = new B;
echo $b->getFoo(); //vypíše bar

Špatné návyky

Zdrojové kódy v této části doma, v práci, ani ve škole nezkoušejte! ;)

PHP kvůli své skriptovací povaze umožňuje v OOP opravdu nehezké věci.

$a = 'foo';
$b = 'bar';

//podmíněné dědění? 11 z 10 programátorů pláče
if ($a == $b) {
    class B {

    }
} else {
    class B extends A {

    }
}

//pokud chci pracovat s nějakým atributem, musí být ve třídě vždy definovaný
class A {
    //fuj!
    if ('foo' == 'bar') {
        public $aaa;
    }

    public function fooBar() {
        if (isset($this->aaa)) { //nesmyslná kontrola, ve správném kódu musí $this->aaa vždy existovat
            //...
        }
    }
}

Dynamické názvy

Pokud k tomu máme důvod, můžeme na základě nějaké proměnné či výpočtu rozhodovat, jaká třída se použije, k jakému atributu se přistoupí a jaká metoda se zavolá.

Musíme být samozřejmě připraveni na všechny hodnoty, kterých daná proměnná může nabýt.

$den = date('D'); //nabývá hodnot Mon až Sun

$a = new $den; //zavolá se třída s názvem dne v týdnu

//od PHP 5.3 lze přistupovat i ke statickým atributům/konstantám/metodám
$den::TRIDNI_KONSTANTA;
$den::methodStatic();

//volání metody
$method = 'mojeMetoda';

//zavolá se instanční metoda mojeMetoda() třídy Thu (pokud je čtvrtek ;))
$a->$method();

//pokud chci zapsat složitější logiku, slouží k tomu složené závorky
$bool = true;

//zavolá se metoda allow() nebo deny()
$a->{$bool ? 'allow' : 'deny'}();

Jmenné prostory (namespaces) (PHP 5.3)

Pokud se vám hodí mít dvě třídy stejného názvu, tak je stačí dát do dvou různých jmenných prostorů a nemáte problém. Ale pozor! Namespaces spíše přidělávají práci, než aby ji ulehčovali. Přesto se dá přijít na způsob, jak práci s namespaces minimalizovat na nejmenší možnou mez.

//lze mít více úrovní jmenných prostorů, \ je oddělovač
namespace Foo\Bar;

//v jiných souborech je nyní ke třídě A nutné přistupovat jako k Foo\Bar\A
class A {

}
namespace MujNamespace;

class B {

    public function methodTest() {
        /**
          * pokud chci uvnitř nějakého namespace přistoupit k úplně jinému namespace,
          * musím zvolit absolutní "cestu" s \ na začátku
          */
        $a = new \Foo\Bar\A;

        /**
          * jinak by došlo k tomuto:
          */
        $a = new A; //třída MujNamespace\A neexistuje!

        /**
          * Pokud na začátku souboru pod definicí namespace zavolám:
          * use \Foo\Bar\A;
          * ...tak už se na třídu mohu odvolávat klasicky:
          */
        $a = new A; //funguje!
    }

}

Late static binding (PHP 5.3)

Problém s překrýváním statických atributů a metod jsem zmínil už výše, zde uvedu jen praktický příklad:

class A {
    protected static $foo = 'foo';

    public function methodLsb() {
        echo self::$foo . "\n";
        echo static::$foo . "\n";
    }
}

class B extends A {
    protected static $foo = 'bar';
}

$b = new B;
$b->methodLsb();

//vypíše:
foo
bar

Dokonalý kontaktní formulář za 10 minut

Nette Framework má mnoho předností. Hlavní z nich je rychlá a efektivní tvorba kvalitních webových aplikací.

Už dávno jsou ty doby, kdy jsem v PHP půl odpoledne patlal obstojný formulář s kontrolou vyplněných textových polí, validitou e-mailové adresy a jakž-takž ucházejícím vzhledem odesílané zprávy.

V Nette je tvorba dokonalého a neprůstřelného kontaktního formuláře práce na 10 minut. Framework totiž obsahuje třídu jak pro práci s formuláři, tak třídu pro odeslání e-mailu (včetně podpory tvorby zprávy šablonovým způsobem).

Jak vypadá zdrojový kód takového formuláře? Vytvoříme si ho v továrničce v Presenteru:

use Nette\Application\UI\Form;
use Nette\Mail\Message;

class ContactPresenter extends BasePresenter
{
    protected function createComponentContactForm() {
        $form = new Form;
        $form->addText('name', 'Vaše jméno')
            ->addRule(Form::FILLED, 'Vyplňte vaše jméno');

        $form->addText('email', 'Váš e-mail')
            ->setEmptyValue('@')
            ->addRule(Form::FILLED, 'Vyplňte váš e-mail')
            ->addRule(Form::EMAIL, 'E-mail má nesprávný tvar');

        $form->addTextarea('text', 'Zpráva')
            ->addRule(Form::FILLED, 'Vyplňte zprávu');

        $form->addSubmit('okSubmit', 'Odeslat');

        $form->onSubmit[] = array($this, 'contactFormSubmitted');
        return $form;
    }
}

Jednoduchým zavoláním makra widget vykreslíme formulář v té šabloně, kde ho chceme:

{control contactForm}

Napíšeme metodu, která se při odeslání formuláře zavolá (a dbáme při tom na pattern Post/Redirect/Get):

public function contactFormSubmitted(BaseForm $form) {
    try {
        $this->sendMail($form->getValues());
        $this->flashMessage('Zpráva úspěšně odeslána!');
        $this->redirect('this');
    } catch (\Nette\InvalidStateException $e) {
        $form->addError('Nepodařilo se odeslat e-mail, zkuste to prosím za chvíli.');
    }
}

Napíšeme samotné odeslání e-mailu (včetně plnění souboru se šablonou):

private function sendMail($values) {
    $mail = new Message;
    $mail->setSubject('Nová zpráva z kontaktního formuláře');
    $mail->setFrom($values['email'], $values['name']);
    $mail->addTo('ondrej@mirtes.cz', 'Ondřej Mirtes');

    $template = $this->createTemplate();
    $template->setFile(__DIR__ . '/../templates/Mail.phtml');

    $template->name = $values['name'];
    $template->email = $values['email'];
    $template->text = $values['text'];

    $mail->setHtmlBody($template);
    $mail->send();
}

A vytvoříme šablonu, jak bude e-mail vypadat (Mail.phtml):

<h3>Nová zpráva z kontaktního formuláře</h3>

<ul>
    <li><strong>Jméno</strong> {$name}</li>
    <li><strong>E-mail</strong> <a href="mailto:{$email}">{$email}</a></li>
</ul>

<h1>Text zprávy</h1>

{$text}

To je celé. Máme formulář, který se neodešle, pokud nejsou vyplněna všechna povinná pole (včetně klientské validace Javascriptem), u kterého nebude hrozit vícenásobné odeslání pomocí F5 a který by nám zabral daleko více času, kdybychom se ho snažili vytvořit bez frameworku.

P.S. Vložit tento článek do mého CMS (tento blog jsem programoval půl roku před tím, než jsem začal s Nette dělat) se ukázalo jako daleko náročnější, než tvorba celého formuláře - neescapuje mi správně znaky (vyřešilo by dibi) a neobarví mi PHP kód (vyřešilo by Texy!). Davide, co bychom bez tebe dělali? :)

P.P.S. Začátkem října jsem celý blog přepsal do Nette, dibi a Texy, takže přesun článku a obarvení kódu v novém systému bylo daleko pohodlnější :)

K nastudování

Jak na kombinaci XML deklarace (prologu) a PHP?

XML deklarace by měla být součástí validní XHTML stránky, i když není povinná.

Problém nastává v momentě, kdy tuto deklaraci chceme použít na začátku PHP skriptu. Jak deklarace XML, tak PHP kód používají < > závorky. PHP se tady snaží překousat kód deklarace, ale neúspěšně - skript se zastaví na první možné řádce.

Jak toto omezení obejít a mít XML deklaraci v libovolném PHP skriptu? Stačí použít echo s jednoduchými uvozovkami:

echo '<?xml version="1.0" encoding="utf-8"?>';

Jak na spam ve webových formulářích?

Jelikož můj původní web poháněl vlastní systém, implementoval jsem do něj postupně funkce tehdy, až když jsem je potřeboval. Když roboti po pár měsících od startu přišli na to, jak přidávat komentáře pod články a ty se začaly objevovat opravdu ve velkém množství, situaci bylo potřeba vyřešit.

Nechtěl jsem uživatele obtěžovat s opisováním znaků z nečitelných obrázků, ani s matematikou. Problém za mě vyřešil Jakub Vrána. Nutno říct, že jeho způsob eliminace spamu u mě fungoval na sto procent, od jeho nasazení mi nepřišel do komentářů jediný nevyžádaný příspěvek (od automatu).

A jak na to? Předpokládáme, že botům stále nebyl dán do vínku JavaScript. Uživateli tedy položíme nějakou otázku či zadáme úkol vyplnit dodatečné pole formuláře, JavaScriptem toto pole skryjeme a požadovanou hodnotou vyplníme. Uživatelé s JavaScriptem o ničem nevědí. Uživatelé bez JavaScriptu nejsou nijak omezeni, pouze musí vyplnit o jednu hodnotu navíc. Ve zpracování odeslaného formuláře pak nepokračujeme, pokud v dané proměnné není požadovaná hodnota.

Část formuláře s ochranným polem

<noscript>
    <label>Vyplňte nospam:</label>
    <input type="text" name="robot" />
</noscript>
<script type="text/javascript">
    document.write('<input type="hidden" name="robot" value="' + 'no' + 'spam' + '" />');
</script>

PHP skript

Zde už následuje pouze jednoduchá podmínka, která nepustí data k zápisu do databáze, pokud formulářové pole neobsahuje řetězec nospam:

if ($_POST["robot"] !== "nospam") { ... }

Ochrana e-mailů proti spambotům pomocí JavaScriptu

E-mailové adresy na webech jsou vystaveny útokům spamrobotů, kteří je indexují a ukládají do své databáze. Stačí mít tedy svůj e-mail 1x na nějakém zaindexovaném webu a už to jede. Obvyklé obrany proti těmto způsobům získávání adres pro spam jsou pro uživatele nepřívětivé. V případě, že je zavináč nahrazen nějakým jiným znakem, nemusí méně zkušeného návštěvníka napadnout při psaní e-mailu ten znak správně nahradit. A v případě adres zobrazených jako obrázky ji musí uživatel přepisovat celou. Navíc už boti disponují kvalitním OCR, na technologii CAPTCHA se nedá spolehnout.

Martin Jurča mi poradil ochranu, kterou sám ke své plné spokojenosti používá. Ta sází na skutečnost, že boti neumí JavaScript. Pomocí JS se zapíše odkaz v normálním tvaru a objeví se na stránce tak, jak ho známe. Uživatelé nejsou omezeni a boti jsou (aspoň na nějakou dobu) eliminování.

Kód pro její implementování na web je následující:

<script type="text/javascript">
    var prikaz = "mail";
    var prikaz2 = "to:";
    var jmeno = "ondrej.mirtes";
    var server = "lasthunter";
    var domena = "cz";
    document.write ('<a href="' + prikaz + prikaz2 + jmeno + '@' + server + '.' + domena + '">');
    document.write (jmeno + '@' + server + '.' + domena + '</a>');
</script>