úterý 21. listopadu 2006

HTML Framework 1 - Select-option

Při programování v PHP se dostanete do fáze, kdy Vás přestane bavit znovu a znovu psát HTML kód pro formulářová políčka. Jedním ze způsobů jak si začít tvořit svou vlastní knihovnu takovýchto HTML komponent, je přejít na OOP kód a v závislosti na zkušennostech si předdefinovat to, co se často opakuje. Tím začnete tvořit svůj malý framework pro HTML výstup vašich webových aplikací.

Je jasné, že existuje spoustu hotových řešení, ale já jsem šel cestou (pro PHPčkáře osobitou) vlastní. Mou snahou je, se odprostit od psaní jakéhokoli HTML kódu při programování základních komponent. Při OOP návrhu tříd je dobré rozvrhnout, které třídy budou znovupoužitelné v jakékoli aplikaci a které nikoli. Jelikož se nyní bavím o HTML výstupu, který je pro PHP jako vzduch pro člověka, je jasné, že se jedná o třídy s vysokou znovupoužitelností.

Vytvořím si adresář /lib ve kterém budu mít třídy, které jsou znovupoužitelné v jakékoli webové aplikaci. Jelikož se jedná o HTML kód a formulářové políčko tak adresářovou strukturu definuji takto: /lib/HTML/Form/

První komponentou bude select-option.

Zkrácená ukázka vlastní třídy v souboru SelectOption.php:

/**
* trida vracejici html select-option
*
* @author Dostal Ales
* @version 1.0
* @date 21.11.2006
* @category lib
* @package HTML
* @subpackage Form
*
*/
class HTML_Form_SelectOption
{

/***************************** ATRIBUTY TRIDY *****************************/

// atribut tridy pro nazev selectu
private $nazev;

// atribut tridy pro data, ktera jsou poslana v poli
private $data;

// atribut tridy pro vybrany radek
private $request;

// atribut tridy pro vytvoreni prazdneho radku v optionu
private $firstNullOption;

// vklada do tagu
*/
public function getSelect()
{
$select = "\n";
return $select;
}


Kompletní třída ke stažení: HTML_Form_SelectOption

Budu nadále předpokládat, že máte vyřešený problém s automatickým nahráváním daných tříd přes __autoload().

Nyní již přichází na řadu malá ukázka, jakým způsobem lze komponentu využívat:

$data = array();
$data = array("ales" => "Aleš", "tomas" => "Tomáš");
$select = new HTML_Form_SelectOption("jmeno", $data, $_GET["jmeno"]);
echo $select->getSelect();


Pokud budu chtít například definovat prázdnou nulovou položku, stačí pred zavoláním o HTML kód přídat:

$select->setFirstNullOption("- jméno -");


Možností je samozřejmě více. Od definování javascriptového kodu do tagu select až po změnu dat, při použití jedné instance. Pokud by daná třída nevyhovovala, není samozřejmě problém přidat jakoukoli novou vlastnost (např. přidání k tagu css třídu, style, apod.).

Definování vlastních HTML komponent programátorovi natolik usnadní práci, že k opakovanému psaní HTML se snad již nikdy nevrátí.

Přístě se podívám na trošku složitější HTML tag: input.

Třída HTML_Form_SelectOption

středa 15. listopadu 2006

Závažná chyba při tvorbě triggerů

Původně jsem chtěl napsat článek o triggerech a jejich využití.
Zjistil jsem ale nepříjemnou chybu, která bez jakýchkoli servítek smaže můj pracně vytvořený trigger, aniž by mi databáze cokoli zdělila.

Jedná se o bug 18153 a jeho duplikace 18816.

Tato nepříjemná chyba mi při provedení dotazu REPAIR/OPTIMIZE/ALTER smaže trigger z information_schema. Problém je o to horší, že použitý název je uložen v souboru *.TRN, který se pro změnu zachová! Takže nyní mám smazaný trigger a navíc nemůžu použít původní název triggeru. Jediné možné řešení je fyzicky smazat soubory *.TRN a vytvořit trigger znovu.

Opět uvedu malý příklad:

CREATE DATABASE stupidni_test;
CREATE TABLE tabulka (
id int NOT NULL auto_increment,
nazev varchar(10) NOT NULL,
PRIMARY KEY(id)
);
CREATE TABLE tabulka_zaloha (
id int NOT NULL auto_increment,
nazev varchar(10) NOT NULL,
PRIMARY KEY(id)
);


A nyní si vytvořím trigger.

DELIMITER $$
/* tento prikaz muze vyhodit chybu, pokud trigger existuje, zde totiz existuje dalsi bug s IF EXISTS */
DROP TRIGGER nefungujes$$

CREATE TRIGGER nefungujes
AFTER INSERT ON tabulka
FOR EACH ROW
BEGIN
INSERT INTO tabulka_zaloha (id, nazev) VALUES (new.id, new.nazev);
END$$

DELIMITER ;


Vše se zdá být v pořádku:

SHOW TRIGGERS;


Nyní přidám jeden sloupeček do tabulky tabulka.

ALTER TABLE stupidni_test ADD COLUMN cislo int default '0' AFTER nazev;


A trigger je pryč:

SHOW TRIGGERS;


Vytvořím si ho znovu:

DELIMITER $$
DROP TRIGGER nefungujes$$

CREATE TRIGGER nefungujes
AFTER INSERT ON tabulka
FOR EACH ROW
BEGIN
INSERT INTO tabulka_zaloha (id, nazev) VALUES (new.id, new.nazev);
END$$

DELIMITER ;


Nevytvořím!

Trigger does not exist
Trigger already exists


Jediné možné řešení je nyní smazat soubor *.TRN nebo vytvořit trigger pod jiným názvem.

Tímto apeluji na všechny, kteří triggery používají: zálohujte si je!

Přístě už se budu věnovat těm triggerům a jejich využití. ;)

pondělí 13. listopadu 2006

MySQL 5.0 díl.2

Typ tabulky InnoDB zvládá i další vlastnost a tou je tzv. transakční zpracování. Ve chvíli, kdy Vaše aplikace bude používat akční dotazy nad důležitými daty, jistě budete hledat způsob, jak tyto data co nejlépe ošetřit.

K tomu dobře poslouží transakce. Setkal jsem se i s názory, kteří transakční zpracování odsuzují z toho či onoho důvodu. Já, jako člověk, který tyto věci využívá v praxi mohu jen podotknout, že je to pro mě určitá záchrana při práci s akčními dotazy.

Co to je, to transakční zpracování dat? Jedná se způsob, pomocí kterého mohu své předešlé kroky navrátit zpět nebo je potvrdit. Série příkazu UPDATE, DELETE, INSERT, které jsou spuštěny při transakčním zpracování, se provedou tehdy, když uznám, že po změně nedojde k znehodnocení dat. Fyzicky se dané akce provedou až po úspěšném ukončení, do té doby jsou dočasně uloženy jen jako série příkazů, které čekají na ukončení. To sebou přináší i tu vlastnost, že zpracování může být v polovině přerušeno (např. výpadkem proudu) a přesto je možné dané úkony dokončit. Je jasné, že tuto vlastnost musíme brát s rezervou a neočekávat vždy spásnou pomoc od nedokončených transakcí. Tím mám na mysli, že se daná série příkazů již nikdy neprovede, nikoli, že by znehodnotila data.

Příkazy pro transakční zpracování jsou následující:

  • start transaction – spouští provedení transakčního zpracování

  • rollback – navrací veškeré změny a uvede data zpět do stavu před spuštěnou transakcí

  • commit – potvrzuje příkazy, zapisuje změny a uvolňuje systémové prostředky potřebné při transakci



Použití transakcí
Domnívám se, že je zbytečné zde vypisovat sérii nějakých akčních dotazu, kde na konci provedu commit či rollback. Spíše než to, je lepší malá ukázka v PHP, jak lze transakce smysluplně využít.

Příklad:

class Error
{
private $error = array();

public function __construct() {}

public function addError($err)
{
if (!in_array($err, $this->error)) {
$this->error[] = $err;
}
}

public function isError()
{
return (boolean) count($this->error);
}

}

$mysqli = new mysqli(/* connect */);
$error = new Error();

$mysqli->query("start transaction");

$query = "UPDATE uzivatele SET prijmeni = 'Novák' WHERE login = 'paveln'";
if ($mysqli->query($query) === false) {
$error->addError($mysqli->error);
}

/* dalsi akcni dotazy */

if (!$error->isError()) {
$mysqli->query("commit");
} else {
$mysqli->query("rollback");
}


Použil jsem zde záměrně vlastní jednoduchou třídu na kontrolu chyb. Je jasné, že příklad je pouze ilustrativní, ale potvrzení transakce mohu provést jedině ve chvíli, kdy jsem si jist, že to má data nijak neznehodnotí. Toto považuji za smysluplné použití transakčního zpracování, kdy pomocí nějaké vlastní aplikace budu rozhodovat, zda sérii příkazů zruším, či potvrdím.

Při práci s tabulkami při transakčním zpracování bychom si měli dát pozor zejména na příkaz: TRUNCATE TABLE, který vymaže data z tabulky a nastaví auto_increment na 1. Po provedení takového příkazu totiž není možné pomocí rollback získat data zpět.

Omezení existuje celá řada, k tomu doporučuji manuál a prostudovat samotné transakce tam.

Poslední věcí o které se zmíním je nastavení automatických transakcí. V souboru my.cnf nalezneme direktivu innodb_flush_log_at_trx_commit, která pokud je nastavena na 1, provádí automaticky transakce. Toto chování se mi zdá nežádoucí a jako takové ho pro jistotu vypínám.
Důvod proč něco takového dělám je rychlost aplikace. Pokud například vkládám velké množství dat, které není nijak zásadní pro běh aplikace a data mám zálohována, provedu úkon bez transakčního zpracování. Jednou ohromnou nevýhodou je totiž extrémní nárůst potřebných systémových prostředků.

Transakce byste měli používat s rozvahou, ale také se jich nebát, protože sebou přináší jistý komfort při modifikaci dat.

V příštím díle se budu věnovat triggerům a důvodům, proč něco takového využívat.

pátek 10. listopadu 2006

MySQL 5.0 díl.1

Od verze 5.0 je možné využívat nových vlastností, které nabízí skutečnou správu dat v databázi.

Jednotlivé vlastnosti rozepíši do vlastních dílů.

V prvním díle se budu věnovat referenčním integritám. Referenční integritu bylo možné využívat již dříve. Respektive od doby, kdy MySQL podporovala typ tabulky InnoDB.

Proč vůbec databáze používá tuto vlastnost? Pokud začnu pracovat s daty v databázi, zjistím, že některé se přímo vážou na jiné. Dříve se používal způsob uchování těchto vazeb v aplikační úrovni.
Nevýhody, které to sebou přinášelo je hned několik:

  • programátor musel stále mít v paměti, která data jsou propojena a podle toho provozoval akční dotazy

  • při změně aplikace nad databází se znovu musely tyto vazby naprogramovat

  • při provedení UPDATE se museli projít všechny vazby a upravit hodnoty

  • a tak dále...



Při použití referenčních integrit v MySQL tyto nepříjemnosti odpadnou.

Jak jsem již psal na začátku, pro využití referenčních integrit musíte použít takový typ tabulky, který danou vlastnost podporuje. Osobně jsem volil nejlepší možnou variantu a to InnoDB.

Lepší než další povídání bude malý příklad:

Mám tabulku uživatelů a tabulku návštěvnosti. Dejme tomu, že u návštěvnosti budu uchovávat informace o počtu návštěv za každý den.

CREATE TABLE uzivatele (
login varchar(10) NOT NULL,
heslo varchar(255) NOT NULL,
jmeno varchar(100) NOT NULL,
prijmeni varchar(100) NOT NULL,
PRIMARY KEY(login)
)ENGINE=InnoDB;
CREATE TABLE uzivatele_navstevnost (
uzivatel varchar(10) NOT NULL,
datum date NOT NULL,
pocet int NOT NULL default '0',
PRIMARY KEY(uzivatel, datum)
)ENGINE=InnoDB;


Vše se zdá být v pořádku. Ale co se stane ve chvíli, kdy některý uživatel změní login? Co se stane ve chvíli, kdy některého uživatele smažu?
Nyní mám dvě možnosti, buď v aplikaci budu udržovat vztah uzivatele:login vs. uzivatele_navstevnost:uzivatel nebo použiji referenční integritu.

ALTER TABLE uzivatele_navstevnost
ADD CONSTRAINT fk_uzivatel_uzivatele
FOREIGN KEY(uzivatel) REFERENCES uzivatele(login)
ON UPDATE CASCADE ON DELETE RESTRICT;


Co jsem v podstatě udělal? Vytvořil jsem cizí klíč v tabulce uzivatele_navstevnost s nazvem fk_uzivatel_uzivatele. Nyní, pokud provedu UPDATE loginu v tabulce uzivatele, změní se mi login uživatele i v tabulce uzivatele_navstevnost. Pokud se pokusím smazat uživatele z tabulky uzivatele, dotaz skončí s chybou. Důvodem je definování DELETE RESTRICT. Zde je jasné, že vlastnost pro DELETE přepíši na CASCADE.

Samotný význam referenčních integrit není usnadnit práci vývojářům při manipulaci s daty, ale udržet data ve správné formě.
Vlastnosti pro UPDATE a DELETE pro cizí klíč jsou následující:

  • CASCADE - provede změny ve sloupci podle změny v referenčním sloupci

  • RESTRICT - znemožní upravit či smazat data

  • SET NULL - při nastaveni refenečního sloupce na NULL či smazání, nastaví se sloupec cizího klíče na NULL (zde samozřejmě je třeba, aby cizí klíč měl možnost obsahovat NULL)



Na předchozím příkladu jsem ukázal možnost vytvoření referenční integrity mezi dvěmi tabulkami, ale existuje možnost vytvoření takovéto integrity v zájmu jedné tabulky.

Příklad:

Budu mít tabulku zaměstnanců, která navíc obsahuje vztah nadřízený-podřízený.

CREATE TABLE zamestnanci (
osobni_cislo int NOT NULL,
jmeno varchar(100) NOT NULL,
prijmeni varchar(100) NOT NULL,
nadrizeny int default NULL,
PRIMARY KEY(osobni_cislo),
CONSTRAINT fk_nadrizeny_zamestnanci
FOREIGN KEY(nadrizeny) REFERENCES zamestnanci(osobni_cislo)
ON UPDATE CASCADE ON DELETE SET NULL
)ENGINE=InnoDB;


S definicí referenčních integrit navíc souvisí i správný návrh databáze. K tomuto problému lze říci jen jedno: mít zkušennosti.

V příštím díle se budu věnovat opět typu InnoDB a další vlastnosti a to transakčnímu zpracování.

čtvrtek 9. listopadu 2006

PHP 5.2

Tak nám vyšla nová verze PHP 5.2, která by měla vylepšit rychlost přímo v ZEND Engine.

Koukal jsem se na změny a je pravda, že pár věcí se zde pohlo k lepšímu. Otázkou však zůstává, zda se z toho paskvilu a neuspořádaného vývoje, v kterém se PHP ocitá dokáže ještě vymanit.

Microsoft sám začal mít o PHP zájem alespoň v podpoře .NET, ale jeden nikdy neví, kam to může zajít (InnoDB v MySQL vs. Oracle).
Jsou zde opravené některé věci, jako např. v abstraktní třídě nemůže existovat statická metoda, magická metoda __toString() nemůže vyhazovat výjimku apod.

Ale co mě stále štve na PHP je ta ignorace type hinting, s tím související přetěžování metod či konstruktorů, dále vyhazování výjimek z php funkcí namísto stupidního Boolean, nemožnost smysluplně vytvořit Singleton, apod.

Pokud se podívám na způsob práce s databází, je často zdůrazněna dobrá podpora MySQL, ale k čemu mi je taková vynikající podpora, když zde nemám pořádný nástroj na ORM, DAO, apod. (např. Hibernate v Jave). Je zde sice jakýsi Propel, ale... Jako standard to bráno není a asi ani nikdy nebude. (Ono MySQL by samo potřebovalo opravit pár věcí, kterými se honosí verze 5. Alespoň integrace nějakého smysluplného jazyka pro psaní stored procedure. V tomto nynějším stavu je to na cvokárnu.)
Jsem zvědavý jaké změny nám přinese PHP 6.0. Těším se zejména na věci jako definování návratových hodnot v metodách, přímou integraci s MySQL a s tím spojený jistý nárůst rychlosti.

Ale na druhou stranu zůstávám skeptický v tom, jestli se PHPko někdy pročistí a vzniknou frameworky, které budou standardem pro práci s danou vrstvou. Pokud ovšem PHP bude i nadále ignorovat type hinting, čistý oop model, zastaralé metody, způsob definování názvů metod, balíčkovací systém pro třídy, apod. tak nikdy nevzniknou frameworky, nikdy se nic nestane standardem a nikdy nebude ničím jiným než jazykem pro lamy :(