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.


Feb 09 2009

Zend Framework: nastavení docType v bootstrapu

Tag: Zend FrameworkJens @ 12:00

Nastavení Doctype aplikace v Zend Frameworku pomocí helperu (placeholder helperu) Zend_View_Helper_Doctype je ideální udělat přímo v bootstrapu. Zend si poté automaticky zajistí (pomocí Zend_Registry), že všechny následně vykreslené helpery budou syntakticky správně – tedy přidá potřebné ukončovací lomítko nepárového elementu, je-li třeba (jedná-li se o XHTML).

Příklad kódu v bootstrapu, který zajistí nastavení doctype (hlavně poslední řádek):

...
// Mvc Layout + adresa pro layouty
Zend_Layout::startMvc(array('layoutPath' => '../application/layouts'));
// xhtml sablona layoutu (musi existovat '../application/layouts/xhtml.phtml'!)
Zend_Layout::getMvcInstance()->setLayout('xhtml');

// nastaveni cesty pro scripty view
$view = Zend_Layout::getMvcInstance()->getView();
$view->setScriptPath(array('../application/views/scripts', '.'));
// nastaveni helperu
$view->setHelperPath('../application/views/helpers', 'Jens_View_Helper');
// nastaveni docType pro celou aplikaci
$view->doctype(Zend_View_Helper_Doctype::XHTML1_STRICT);
...

V šabloně ../application/layouts/xhtml.phtml, si pak přímo na začátek přidáte voláni vykreslení doctype jednoduchý voláním $this->doctype() bez parametru, a on už bude sám vědět co má vypsat :) Příklad xhtml.phtml layoutu pak vypadá přibližně takto:

<?=$this->doctype()?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="cz" lang="cz">
	<?=$this->layout()->header?>
	<body>
		<?=$this->layout()->content?>
	</body>
</html>

Feb 07 2009

headLink a headStyle View Helpery

Tag: Zend FrameworkJens @ 17:00

Jak přidat CSS soubory nebo ikonu do hlavičky (X)HTML v Zend Frameworku? Už od delší dobu je přímo v Zendu k dispozici spousta View Helperů (headLink, headMeta, headScript, headStyle a headTitle). Dnes se zaměříme jen na dva: headLink a headStyle.

Přidání CSS souboru

Přidání CSS souboru lze klasicky dvojím způsobem, první:

<style media="all" type="text/css">@import "/css/style.css";</style>

a druhý:

<link rel="stylesheet" href="/css/style.css" type="text/css"/>

Oba dva jsou hojně používání na různých webech. Který z nich je nejideálnější či úplně nejoptimálnější (:-) nevím, asi se zrovna používá to, co se komu líbí.

V Zendu budete pro první případ potřeboval helper headStyle:

echo $this->headStyle()->appendStyle('@import "/css/style.css";', array('media' => 'all'));

a pro druhý pak využijete headLink:

echo $this->headLink()->appendStylesheet('/css/style.css', 'all');

Oba použití samozřejmě můžete řetězit a celek vypsat pouze jednou:

$this->headLink()->appendStylesheet('/css/style.css', 'all');
$this->headLink()->appendStylesheet('/css/layout.css', 'all');
echo $this->headLink(); // vypise oba dva styly

Přidání favicon(y)

Na přidání favicony se používá pouze element <link>, takže tedy helper headLink:

echo $this->headLink()->headLink(
	array(
		'rel' => 'favicon',
		'href' => '/img/favicon.ico',
		'type' => 'image/x-icon'
	)
);

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();

Dec 11 2008

Zend Framework: Hostname routing

Tag: SEO,Zend FrameworkJens @ 23:11

O routování subdomén v Zend Framework (ZF) jsem už v jednom článku psal. Kolega mě upozornil, že v současném ZF 1.7.0 již takové routování je v podobě třídy Zend_Controller_Router_Route_Hostname. V době psaní předchozího článku byla k dispozici pouze verze ZF 1.5.0, která routování domén a subdomén nepodporovala. Prošel jsem proto dokumentaci ZF abych zjistil, od kdy je tato nová vlastnost podporována.

První zmínka je ve verzi ZF 1.5.3 (2008-07-28) -viz zend.controller.router.html, kapitola Hostname routing staticky:

$route = new Zend_Controller_Router_Route(
    array(
        'host' => 'blog.mysite.com',
        'path' => 'archive'
    ),
    array(
        'module'     => 'blog',
        'controller' => 'archive',
        'action'     => 'index'
    )
);
$router->addRoute('archive', $route);

a dynamicky:

$route = new Zend_Controller_Router_Route(
    array(
        'host' => array(
            'regex'   => '([a-z]+).mysite.com',
            'reverse' => '%s.mysite.com'
            'params'  => array(
                1 => 'username'
            )
        ),
        'path' => ''
    ),
    array(
        'module'     => 'users',
        'controller' => 'profile',
        'action'     => 'index'
    )
);
$router->addRoute('profile', $route);

V této verzi se tedy používal standardní Zend_Controller_Router_Route rozšířený o možnosti routovaní domén a subdomén.

Zend_Controller_Router_Route_Hostname

Překvapující je, že kapitola o routování domén a subdomén mizí od dokumentace ZF 1.6.2 do verze ZF 1.7.0 Preview. Že by chybka? Nebo záměr? Další zmínka se objevuje až v současné verzi online dokumentace, kdy je aktuálně k dispozici Zend Framework 1.7.1. V současné verzi to vypadá tak, že princip routování domén a subdomén doznal zásadních změn a proto byl rozdělen a vznikla vlastní třída pro Zend_Controller_Router: Zend_Controller_Router_Route_Hostname.

Použití je poměrně jednoduché, a můžete routovat všechno včetně třeba TLD:

$routa = new Zend_Controller_Router_Route_Hostname(
	':subdomena.domena.cz',
	array(
		'controller' => 'index',
		'action'     => 'index'
	)
);
$router->addRoute('hostname', $routa);

Takto nadefinovaná routa začne fungovat na všechny URL s definovanou subdoménou, tedy např.: http://www.domena.cz ale nebude fungovat na http://domena.cz – neobsahuje subdoménu.

Zajímavější je však již zmíněná možnost routovat doménu druhého i prvního řádu (TLD):

$routa = new Zend_Controller_Router_Route_Hostname(
	':subdomena.:domena.:tld',
	array(
		'controller' => 'index',
		'action'     => 'index'
	)
);
$router->addRoute('hostname', $routa);

což funguje úžasně, přesně podle očekávání, takže na URL http://www.domena.cz (v IndexController.php a action indexAction vypíšeme $this->getRequest()->getParams()):

array (
  'subdomena' => 'www',
  'domena' => 'domena',
  'tld' => 'cz',
  'controller' => 'index',
  'action' => 'index',
)

V dokumentaci Zendu ještě navíc doporučují použít zřetězení této routy s nějakou předchozí definovanou „path“ routou proto, aby se tyto parametry nepřenášely pro každé domény či subdomény ale pouze pro určité URL.

A na závěr klasicky, oficiální odkaz on-line dokumentaci zendu.


Nov 17 2008

Vyšel Zend Framework 1.7.0

Tag: Zend Framework,ZprávičkyJens @ 23:30

Dnes, tedy 17.11.2008 vyšel nový „stabilní“ Zend Framework.

V oficiální zprávičce si můžete přečíst všechna nová rozšíření a vylepšení, zdá se že je toho docela dost a proto bych doporučoval s upgradem ještě chvíli počkat, než si to sedne a vychytají se mouchy :)

Co mě zaujalo z výčtu novinek/změn nejvíce:

  • Výkonnostní vylepšení v Zend_Loader, Zend_Controller
  • Zend_Config_Writer
  • File transfer enhancements

Více viz již zmíněné oznámení o vydání ZF 1.7.0.


Apr 29 2008

Zend Framework: View Helpers

Tag: Zend Framework,ZprávičkyJens @ 14:31

Od verze 1.5 jsou součástí Zend Frameworku i nové View Helpry. Některé jsou pouze upravené ale spousta jich je přidána. Na Zend Developer Zone včera vyšel článek View Helpers in Zend Framework, který se věnuje přehledu nových helperů.

V současnosti jsou helpry rozděleny do třech kategorií:

  1. Formulářové helpry – slouží pro generování formulářů a elementů spojených s formuláři
  2. Placeholders helpry – slouží pro generování struktury stránky, jež je poskládaná z jednotlivých segmentů do kterých lze ukládat (a načítat) obsah nezávisle. Nakonec se tyto segmenty (typicky s využitím Zend_Layout) vykreslí jako ucelená stránka.
  3. Utility helpry – používají se především pro přístup k ostatním komponentám ZF jako např. layout (Zend_Layout), překlady (Zend_Translate), generování URL na základě aktuálního routování (Zend_View_Url_Helper), atd.

Zajímavostí jsou i tzv. Partials helpry, které patří do třetí kategorie: Utility helprů. Ty při renderování používají vlastní prostor proměnných který si definujete. Tedy namísto:

$this->view->foo = 'neco';
$this->view->render('bar.phtml');

použijete toto:

$this->view->partial('bar.phtml', array('foo' => 'neco-jineho'));

To lze s výhodou použít v případě, kdy chcete například v cyklu renderovat stále jednu šablonu a nechcete si přepisovat nějaké „globální“ View proměnné.

Další podrobnosti najdete ve výše zmíněném článku a samozřejmě také na webu dokumentace (který je prý celý postavena na ZF :-) Zendu — Zend_View.


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 18 2008

Vyšel Zend Framework 1.5

Tag: Zend Framework,ZprávičkyJens @ 18:20

Včera, tedy 17.3.2008, vyšla další významná stabilní verze Zend Frameworku, v tuto chvíli tedy již 1.5.0.

Hlavní novinky jsou:

  • přidána komponenta Zend_Form pro generováni, validaci a práci s formuláři včetně podpory Ajaxu (Zend_Form tutoriál)
  • přidána komponenta Zend_Layout a související úprava Zend_View pro snazší práci s šablonami

Dále byla přidána podpora OpenID a Microsoft’s InfoCard přes Zend_Auth komponentu, vylepšena byla podpora UTF-8 pro generování PDF, přidány funkce pro lepší integraci Ajaxu a přidány funkce pro práci se službami Google.

Více se dočtete v oficiálním ohlášení. Současně s tímto byl také přepracován web po grafické stránce pro snadnější navigaci. Takže neváhejte, brouzdejte a upgradujte ;)