HTTP routování v CLI
Potřebujete spouštět různé úlohy z příkazové řádky, ve kterých je třeba tvořit url adresy nadefinované routami, a nevíte jak na to? Čtěte dále…
Problém
Jak docílím routování na správné presentery a akce v CLI módu (příkazová řádka) stejně jako u HTTP (url adresa), abych nemusel psát url adresy „natvrdo“ a těžce vypocené routy tak nepřišly vniveč?
Nejjednoduší je problém vysvětlit na nějakém příkladě:
- Řekněme že máme nějaký cron (úloha běžící na pozadí), který odesílá emaily uživatelům s odkazem na stránku přihlášení.
- Cron běží v CLI módu, jehož routováni nám zajištuje
Nette\Application\CliRouter. - Dále máme routy pro HTTP požadavky tvořené
Nette\Application\Route, které popisují jak má vypadat url adresa pro nějakou akci.
Náš příklad obsahuje tři akce. Dvě pro HTTP požadavek a jednu pro CLI požadavek.
Jednoduchý Presenter obsluhující tyto tři akce by mohl
vypadat následovně:
use Nette\Application\Responses\TextResponse,
Nette\Security\AuthenticationException;
class DefaultPresenter extends BasePresenter
{
public function renderDefault()
{
$source = '<h1>Úvodní stránka</h1><a href="' . $this->link('login') . '">Přihlášení</a>';$
$this->sendResponse(new TextResponse($source));
}
public function renderLogin()
{
$source = '<h1>Přihlášení</h1>';
$this->sendResponse(new TextResponse($source));
}
public function actionCron()
{
if (!$this->getContext()->params['consoleMode']) {
throw new AuthenticationException;
}
$link = $this->link('//login');
// odeslani napr. mailu s linkem
echo $link;
$this->terminate();
}
}
Routy by pak mohly vypadat takto:
use Nette\Application\Routers\Route,
Nette\Application\Routers\CliRouter;
if ($container->params['consoleMode']) {
$application->allowedMethods = FALSE;
$router[] = new CliRouter('Default:default');
} else {
$router[] = new Route('index.php', 'Default:default', Route::ONE_WAY);
$router[] = new Route('prihlaseni', 'Default:login');
$router[] = new Route('<presenter>/<action>[/<id>]', 'Default:default');
}
CliRouter definovaný výše mi nefungoval,
následující řešení ano. (MartyIX k 4.10.2011)
$this[] = new CliRouter(array("action"=>'Default:default'));
Přes webový prohlížeč se dostaneme k akcím:
default→http://example.com/login→http://example.com/prihlaseni
Přes příkazovou řádku se dostaneme k akci:
cron→C:\test-cli-routing\www>php index.php Default:cron
O routování HTTP požadavků se nám postará Nette, které zjistí na jaké doméně je akce volána, jaký je použit protokol, port, atp. Problém ale je, že v režimu příkazové řádky HTTP požadavek neexistuje, tudíž Nette nemá jak zjistit doménu, protokol, apod.
Řešení
Řešení je velmi jednoduché. Je potřeba „podstrčit“ Nette vlastní nadefinovaný objekt reprezentující HTTP požadavek. Nette s ním pak pracuje jako by si ho vytvořil sám ;-).
Díky tomu že Nette používá k získaní objektu Nette\Http\Request
službu, k jejiž inicializaci je použita továrna, není nic jednoduššího
než říct Nette: „Pro vytvoření Http\Request nevolej svou
továrnu, ale volej mou lepší továrnu :-)“
A v hledem k tomu, že další text by byl zbytečný, tak zde je
upravený bootstrap.php, který se o vše postará:
use Nette\Diagnostics\Debugger,
Nette\Utils\Strings,
Nette\Application\Routers\Route,
Nette\Application\Routers\CliRouter,
Nette\Application\Routers\SimpleRouter,
Nette\Http\Url,
Nette\Http\UrlScript,
Nette\Http;
// Load Nette Framework
$params['libsDir'] = __DIR__ . '/../libs';
require $params['libsDir'] . '/Nette/loader.php';
// Enable Nette Debugger for error visualisation & logging
Debugger::$logDirectory = __DIR__ . '/../log';
Debugger::$strictMode = TRUE;
Debugger::enable();
// Load configuration from config.neon file
$configurator = new Nette\Configurator;
$configurator->container->params += $params;
$configurator->container->params['tempDir'] = __DIR__ . '/../temp';
$container = $configurator->loadConfig(__DIR__ . '/config.neon');
// Registrace vlastni tovarny na vytvareni 'Nette\Http\IRequest'.
if ($container->params['consoleMode']) {
$container->removeService('httpRequest');
$container->addService('httpRequest', function() {
// Podle potreby muzeme pouzit nastaveni z configu nebo vzit z parametru prikazove radky, aj.
$uri = new UrlScript;
$uri->scheme = 'http';
$uri->port = Url::$defaultPorts['http'];
$uri->host = 'example.com';
$uri->path = '/';
$uri->canonicalize();
$uri->path = Strings::fixEncoding($uri->path);
$uri->scriptPath = '/';
return new Http\Request($uri, array(), array(), array(), array(), array(), 'GET', null, null);
});
}
// Init application
$application = $container->application;
// Setup router
$router = $container->router;
if ($container->params['consoleMode']) {
$application->allowedMethods = FALSE;
$router[] = new CliRouter('Default:default');
} else {
$router[] = new Route('index.php', 'Default:default', Route::ONE_WAY);
$router[] = new Route('prihlaseni', 'Default:login');
$router[] = new Route('<presenter>/<action>[/<id>]', 'Default:default');
}
// Configure and run the application!
$application = $container->application;
//$application->catchExceptions = TRUE;
$application->errorPresenter = 'Error';
$application->run();
tom | 29. 8. 2011, 21:14 | question
lze nejak prepsat tato konstrukce
$container->addService(‚httpRequest‘, function() {…});
pro starsi verzi PHP 5.2 ? hlasi mi to u me, v tomto problem.
„Language feature not compatible with PHP version …“
diky