Url, Routy a filtry

13. 12. 2015

Dokumentace Nette stále není na rozumné úrovni a nemusí z ní být úplně pochopitelné, jak na tak triviální věc, jak jsou "cool" url. Potom zbytečně zabíjí čas hledáním a zkoušením.

Tedy, pro začátek zničíme statické volání továrničky na router (RouterFactory) a router budeme vytvářet přes její instanci:

services:
    - App\RouterFactory
    router: @App\RouterFactory::createRouter

A hned i ukázku RouterFactory:

namespace App;

use Nette;
use Nette\Application\Routers\RouteList;
use Nette\Application\Routers\Route;


class RouterFactory
{

    private $urls = [
        'hello-kitty-hrnecek' => [
            'presenter' => 'Products',
            'action' => 'detail',
            'id' => '1010',
        ],
        'hello-kitty-propiska' => [
            'presenter' => 'Products',
            'action' => 'detail',
            'id' => '1234',
        ],
    ];


    /**
     * @return Nette\Application\IRouter
     */
    public function createRouter()
    {
        $router = new RouteList;

        $router[] = new Route('[<url [a-z-0-9\/]+?>]', [
            NULL => [
                Route::FILTER_IN => [$this, 'urlIn'],
                Route::FILTER_OUT => [$this, 'urlOut']
            ]
        ]);

        return $router;
    }


    public function urlIn($params)
    {
        $url = trim($params['url'], '/');

        if (isset($this->urls[$url])) {
            return $this->urls[$url];
        }

        return NULL;
    }


    public function urlOut($params)
    {
        foreach ($this->urls as $url => $url_params) {
            if ($params == $url_params) {
                return ['url' => $url];
            }
        }

        return NULL;
    }

}

Co to dělá? V poslední době už je v dokumentaci zmíněna sekce "globální filtry". Znamená to, že pokud takové filtry vytvoříme (jako v příkladu výše), dostaneme ve filtru _IN pole s url, přičemž musíme vrátil pole minimálně s prvky presenter a action a ve filtru _OUT dostaneme alespoň presenter a action a vracíme url - v poli. Zkuste si dumpnout argumenty funkcí urlIn a urlOut a pochopíte snad lépe.

Dále je třeba brát na vědomí několik věci:

1, Seznam url a jejích parametrů (tedy obsah property ::$urls) je vhodné vést v nějakém persistentním úložišti a opět ho nějak cachovat.

2, Opatrně na restrikci query (a jiných) parametrů, které mohou být smazány v metodě urlOut - Každý presenter, pokud má zděděnou property $autoCanonalize == TRUE, se bude pokoušet při každém requestu zpětně vyrábět z routerem vrácených parametrů url, aby případně přesměroval na kanonickou url. V tem moment se mohou některé query parametry (nebo i pojmenované parametry) ztratit při redirectu (například parametr do, zajišťující funkčnost signálů, nebo fid, který používají flashMessages).

3, Pokud znáte (například) staré existují url, ze kterých budete provádět přesměrování na nové, je pro přehlednost lepší navěsit listener na událost Nette\Application\Application::$onError, než vyrábět routery ze starých url a pak je cpát na nějaké "slunné" místo v aplikaci. Pokud vám skončí aplikace neexistující stránkou, spustí se váš error listener a stavový kód můžete ověřit v argumentu vašeho onError listeneru: if (404 === $exception->getCode()) {, zkus najít redirect na novou url pomocí staré. Kde vezmu starou url? Do listeneru si předám http request..

Přidat komentář


Není nastavení cache jako nastavení cache

21. 11. 2015

Nette umožňuje některým svým věcem (nechci říct službám, ani komponentám, prostě různým věcem) nastavit žádnou cache, tedy Nette\Caching\Storages\DevNullStorage. Bohužel, ne vždy je to pravda a ne vždy to musí fungovat.

Latte

Řekneme Latte, aby se necachovalo:
services:
	nette.latteFactory:
		setup:
			- setTempDirectory(NULL)
Zároveň přidáme nějaká makra aplikace:
latte:
	macros:
		- App\Utils\Macros

Spustíme aplikaci, makra fungují jak mají. Jdeme datlit - dnes je in cpát všechny kousky aplikace (aplikací) do composer balíčku, tedy vytvoříme samostatný repozitář na užitečná Latte makra a postneme ho na facebook. Kámen úrazu přichází ve chvíli, kdy se radujeme z vlastní tvořivosti při vytváření nových jmenných prostorů právě založeného repozitáře. Pojmenujme nový namespace našich čupr maker Cupr\Macros. V ten moment musíme pozměnit namespace maker v configu naší aplikace:

latte:
	macros:
		- Cupr\Macros
Světe div se, při spuštění aplikace na nás svítí Tracy, že prej Class App\Utils\Macros not found. Takže Latte cachuje svoje věci nehledě na konfiguraci. Ta ve skutečnosti mění nastavení cachování jen {cache} maker. Haha, paradox. Tak smažte cache a apliakce zase pojede jako za mlada.

DIC

Používá vlastní cachovací mechanizmus. Tečka.

NDBT

Třídy NDBT, které potřebují cachovat, vyžadují od DIC Nette\Caching\Istorage, takže ten se dá podstrčit v configu (jak to píše dokumentace):

services:
	cacheStorage:
		class: Nette\Caching\Storages\DevNullStorage

Good, vyřešeno.

RobotLoader

RobotLoader si vytváří cache ještě před zpracováním configu(ů), což dává smysl, takže mu je též u zadku, jestli po něm v configu něco chcete. Pozor, to se týká jen configu. Chování upravit lze pomocí Nette\Loaders\RobotLoader::setCacheStorage( Nette\Caching\IStorage $storage ).

Aplikační cache

Služby aplikace budou v jednodušších případech vyžadovat od DIC Nette\Caching\IStorage, stejně jako například NDBT.


Sečteno, podtrženo - chování cache můžete ovlivnit jen u svých služeb a NDBT. Makra {cache} nikdo nepoužívá.

Přidat komentář


FizzBuzz without ifs in 90 characters

16. 7. 2015

V návaznosti na tento post jsem si sem přidal své varianty s tím, že nad nimi budu dumat dál, až bude čas. (...)

PHP: 76

for($i=1;$i<101;$s=['Fizz'][$i%3].['Buzz'][$i%5],print[$i++,$s][!!$s]."\n");

Ruby: 78

(1..100).map{|i|puts (['Fizz'][i%3].to_s+['Buzz'][i%5].to_s).sub(/^$/,i.to_s)}

JavaScript: 79

for(var i=1;i<101;i++){console.log((['Fizz'][i%3]||'')+(['Buzz'][i%5]||'')||i)}

Komentáře (2) Přidat komentář


ErrorPresenter vs persistentní parametry

26. 6. 2015

Aplikace je lokalizovaná a Nette nám persistentní parameter (v basePresenteru) $locale hezky pži každém requestu automaticky naplňuje. To je fajn, máme ho všude k dispozici. Po nějaké době chceme lokalizovat i chybové stránky. Otevřeme si ErrorPresenter, abychom načítali pokaždé jinou šablonu. Ujistíme se, že dědí od BasePresenteru a vesele začneme $this->locale používat. Jak nejrychleji nasimulovat chybu? Zapneme produkční prostředí a odkážeme na neexistující URL. Hele, nefunguje to. Po několikaminutovém hledání a zkoumání si potvrdíme, že do ErrorPresenteru se opravdu persistentní parametry nepředávají (v tomto případě).

Application\Application

Popišme si dvě situace, při kterých může dojít k "vyhození erroru".

  1. Chyba během aplikace - například zavolání neexistující metody $this->hello(); v HomepagePresenteru
  2. Chybná URL (Bad request)

Ad 1, $container->getByType('Nette\Application\Application')->run() - Tento kousek kódu zná každý. V metodě Application::run() se podle Htttp\Request vytvoří Application\Request, který už vede na konkrétní Presenter. Na Presenteru se pak zavolá ::run(). My jsme však v Presenteru způsobili chybu zavoláním neexistující metody...

Ad 2, Application::run() běží stejně, jako v prvním případě. Aplikace chce vytvořit Application\Request, ale už tady selže, bum...

Předchozí situace se rozejdou na řádku Application.php s voláním $this->processRequest($this->createInitialRequest());. Poprvé se stihne úspěšně zavolat Application::createInitialRequest() a teprve při zpracování požadavku aplikace umře. Podruhé Application::processRequest() nevrátí požadovaný request s patřičnými parametry - těsně před vrácením requestu vyhodí BadRequestException. V první situaci se tedy do Presenteru stihnou propsat náležité parametry a v ErrorPresenteru můžeme lokalizovat pomocí persistentní property $locale. A teď bychom rádi dostali parametr i do druhé situace.

Routříčku, otřes se

Application\Request se tvoří na začátku metody Application::createInitialRequest() nehledě na to, zda je cíl požadavku dostupný či ne. Pokud máme připravenou nějakou "fallback routu" pro určení alespoň lokalizace (na základě hostu, apod), můžeme se pokusit získat znovu Application\Request, aniž bychom se ho snažili protlačit k životu. Někde tedy znovu zavoláme Application::router->match(Http\Request); a pokusíme se ve vráceném požadavku najít naše parametry.

Nette\Object

Ať už pomocí Kdyby\Events, nebo jinak si zaregistrujeme událost Application::onError. Událost se v případě chyby spustí a jako argument dostaneme instanci Application a vyhozenou Exception. Událost bude muset odněkud získat Http\Request. Pomocí knihovny Kdyby\Events si ji jednoduše předáme pomocí konstruktoru. Když máme událost připravenou, něco do ní napíšeme:

public function onError(Application $application, Exception $exception)
{
	$application_request = $application->router->match($this->httpRequest);
	$request_parameters = $application_request->getParameters();
	$locale = $request_parameters['locale'];
}

Tím jsme sice získali kýžený parametr, ale v ErrorPresenteru stále není. Nejjednodušší cestu vidím v uložení parametru do session (kterou si předáme konstruktorem) a načtení parametru ze session v ErrorPresenteru. Cest je samozřejmě mnoho. Vyberte si tedy vaši.

Přidat komentář


Zrychlujeme!

29. 4. 2015

Kdo by rád neporovnával benchmarky PHP frameworků... Dnes jsem zavadil o Laravel Lumen. Ideální pro tvorbu API, jak píše autor Taylor Otwell. Zároveň uvádí, že Lumen zvládne kolem "1900 requests per second". Jistě, týká se to out-of-box frameworku, ale i tak je to pozoruhodné, nebo spíš zajímavé číslo. Pojďme si vzít stopky do ruky sami.

Ahoj světe, Nette

Začneme, jak jinak, Nette Sandboxem. Standardní composer create-project nette/sandbox odpovídá na cca 108 requestů/s. Nette Microframework si s odpovědí pospíší - čas requestu je přibližně poloviční. Abychom však porovnávali s Lumenem čísla ve stejném prostředí, musíme stopnout čas i jemu na našem železe. Stejně jako dosud testované frameworky i tento pustíme bez jakýchkoliv APC a jiných cachí. Podívejme, Lumen odpoví po cca 13 ms. Počet requestů za sekundu vyjde na 763. "Velký" Laravel je na tom přitom znatelně hůře než Nette Sandbox - 35 requestů/s s průměrným časem odezvy 284 ms.

Na čem to všechno "jedu"

PHP 5.6, Vagrant 1.7.2, NGINX 1.6.2, Virtualbox 4.3, 2GB RAM. Na Macbooku (mid-2012) běží ještě další servery a nějaká multimédia, klasika

HHVM

Pokud za NGINX ještě zřetězíme HHVM, Sandbox Nette zvládne přibližně 160 requestů/s, průměrný request 63ms, micro verze je víe jak dvojnásobně rychlejší. Lumen se vyhoupne na zhruba 1050 requestů/s s průměrnou dobou 10ms.

Edit: Pro srovnání jsem přidal ještě framework Phalcon. Srovnávat Phalcon s/bez HHVM nemá smysl, ve výsledcích je tedy zahrnut pouze benchmark bez HHVM..

Kompletní výsledky

Bez HHVM:
	Laravel Lumen:
		Requests per second:    762.79
		Time per request:       13.110 [ms]

	Laravel:
		Requests per second:    35.18
		Time per request:       284.238 [ms]

	Nette Sandbox:
		Requests per second:    108.78
		Time per request:       91.929 [ms]

	Nette Micro:
		Requests per second:    219.15
		Time per request:       45.630 [ms]

	Phalcon:
		Requests per second:    1452.03
		Time per request:       6.887 [ms]

HHVM:
	Laravel Lumen:
		Requests per second:    1044.99
		Time per request:       9.569 [ms]

	Laravel
		Requests per second:    57.55
		Time per request:       173.762 [ms]

	Nette Sandbox:
		Requests per second:    158.25
		Time per request:       63.191 [ms]

	Nette Micro:
		Requests per second:    367.35
		Time per request:       27.222 [ms]

Komentáře (8) Přidat komentář

Tento web používá k poskytování služeb a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tím souhlasíte. V pořádku Další informace