Odkazy ve tavru: clanky/7-titulek-clanku

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
motorcb
Člen | 552
+
0
-

Zdravim.

Chtěl bych mít odkazy ve tvaru: clanky/7-titulek-clanku

Nyní je mám ve tvaru: clanky/id:

$router[] = new Route('clanek/<id>', 'Clanek:detail');

Jak udělám aby v adrese byl i titulek článku?
Titulek si zakoduju funkci webalize

return Strings::webalize($titulek);

Ale jak ho v routeru dostat do adresy?

Adresy mam ve tvaru:

<a href="{link Clanek:detail, 7}">Titulek článku/a>
Michal Vyšinský
Člen | 608
+
0
-

Routing – masky

Příklad, který mám použitý na svých webech:

/*
 * Pages route - /<id>-<title>
 */
Route::addStyle("title");
Route::setStyleProperty("title", Route::FILTER_OUT, function($url) {
            return \Nette\Utils\Strings::webalize($url);
        });
Route::setStyleProperty("title", Route::FILTER_IN, function($url) {
            return \Nette\Utils\Strings::webalize($url);
        });


$router[] = new Route("clanky/<id [0-9]+>-<title>", array(
    "presenter" => "Clanek",
    "action" => "detail",
    "id" => NULL
        ));

EDIT:
Link pak vypadá takto:

<a href="{link Clanek:detail, id=>7, title=>'Titulek článku'}">Titulek článku/a>

EDIT 2:

Titulek si zakoduju funkci webalize

Toto pak není potřeba. Do linku můžeš dávat přímo titulek, maska se ti postará o správnou funkčnost.

Editoval CherryBoss (18. 2. 2013 13:13)

motorcb
Člen | 552
+
0
-

2CherryBoss:

Díky :)

Jenže to budu muset předělat všechny odkazy aby v nich bylo title=>‚…‘ :(

<a href="{link Clanek:detail, id=>7, title=>'Titulek článku'}">Titulek článku/a>

Nebylo by možné ten titulek nastavovat přímo v routeru?
Představoval bych si přes FILTER_OUT?. Vytáhnout informace o článku a titulek přidat do adresy

Michal Vyšinský
Člen | 608
+
0
-

Tak to už musí poradit zkušenější. Ale obávám se, že to nijak jednoduše nepůjde.
Edit: proč si myslím, že to nelze: Nemáš se jak dostat k parametru „id“.

Editoval CherryBoss (18. 2. 2013 13:40)

tomas.lang
Člen | 53
+
0
-

motorcb: Možné to je, nejlépe pak jít cestou vlastního routeru (i když možná je to v tomto případě kanón na vrabce… :) ):

<?php

class MySpecialRouter extends Nette\Application\Routers\Route
{

	protected $database = NULL;

	public function injectDatabase(Nette\Connection\Database $database)
	{
		$this->database = $database;
	}

	public function constructUrl(Nette\Application\Request $appRequest, Nette\Http\Url $refUrl)
	{
		// upravím parametry
		$params = $appRequest->getParameters();

		if ($this->database !== NULL) {
			$articleRecord = $this->database->table('clanky')->get($params['id']);

			if ($articleRecord !== FALSE)
				$params['title'] = Nette\Utils\Strings::webalize($articleRecord->title);
			else
				$params['title'] = NULL;
		}

		$appRequest->setParameters($params);

		// vytvořím url
		return parent::constructUrl($appRequest, $refUrl);
	}

}

?>

No a routování pak provedu následovně:

<?php

$route = new MySpecialRouter('clanek/<id>[-<title>]', 'Clanek:detail')
$route->injectDatabase($this->context->database);

?>

Píšu to teď z patra, takže to nemám ozkoušené, ale mělo by to fungovat… :-)

Editoval tomas.lang (18. 2. 2013 14:32)

motorcb
Člen | 552
+
0
-

tomas.lang:

Paráda! To vypadá dobře!
Povedlo se. díky

Editoval motorcb (18. 2. 2013 16:18)

Michal Vyšinský
Člen | 608
+
0
-

Inject přes konstruktor?

Edit: vidím, že už vyřešeno :-)

Editoval CherryBoss (18. 2. 2013 16:21)

Lumeriol
Generous Backer | 63
+
0
-

tomas.lang napsal(a):

motorcb: Možné to je, nejlépe pak jít cestou vlastního routeru (i když možná je to v tomto případě kanón na vrabce… :) ):

No a routování pak provedu následovně:

<?php

$route = new MySpecialRouter('clanek/<id>[-<title>]', 'Clanek:detail')
$route->injectDatabase($this->context->database);

?>

Píšu to teď z patra, takže to nemám ozkoušené, ale mělo by to fungovat… :-)

A jak jednoduše přidat tyto routy do RouterFactory bys netušil? Tam se ke contextu Nette nedostane.

Lopata
Člen | 139
+
0
-

Ta metoda createRouter (viz sandbox) může přijímat jakýkoliv argument, přičemž normálně funguje autowiring. Tak si tam prostě injectni database. Nebo lépe, nějakou obálku, která pracuje s cache a nebude se ptát databáze při každém odkazu.

Lumeriol
Generous Backer | 63
+
0
-

Lopata napsal(a):

Ta metoda createRouter (viz sandbox) může přijímat jakýkoliv argument, přičemž normálně funguje autowiring. Tak si tam prostě injectni database. Nebo lépe, nějakou obálku, která pracuje s cache a nebude se ptát databáze při každém odkazu.

A nebyl by nějaký drobný praktický příklad? Zkoušel jsem hledat vytvoření injectu tímto způsobem, ale marně a myslím že by to ocenili i další.

Lopata
Člen | 139
+
0
-

Když se v sandboxu podíváš do souboru config.neon , uvidíš, že se tam ta RouterFactory deklaruje jako service. Díky tomu na ní funguje autowiring.

Autowiring je mechanismus, který u konstruktorů (i u jiných metod, ale to zatím asi neřeš) podle type hintů pozná, co ten či onen objekt chce a pokud najde odpovídající službu, předá ji jako příslušný argument, a to aniž bys to ty musel explicitně příkázat. Od toho se to jmenuje autowiring.

My tedy chceme router naučit překládat ty adresy. Víme, že aplikace se může kdykoliv rozrůst, a že tudíž hrozí rozsáhlost a složitost té třídy RouterFactory. Uděláme si proto nějaký pomocný objekt, který nám s tím překladem pomůže, abychom odlehčili oné nebohé továrničce na routy. Ten pomocný objekt tedy deklarujeme jako službu přidáním tohoto řádku do config.neon:

services: # Tento řádek už tam je. Neduplikuj to, jen tam přidej další záznam.
	cachedDatabaseRouterHelper: my\Namespace\CachedDatabaseRouterHelper

Nyní musíme tu třídu vytvořit.

namespace my\Namespace;

use Nette;

class CachedDatabaseRouterHelper extends Nette\Object {

	protected $connection;

	protected $cache;

	public function __construct(\DibiConnection $connection, Nette\Caching\Storages\FileStorage $storage) {
		$this->connection = $connection;
		$this->cache = new Nette\Caching\Cache($storage, 'CachedDatabaseRouterHelper'); // Klidně té Cache dej lepší namespace… ;)
	}

	public function filterIn($param) {
		// tady regulárem vybereš to ID
	}

	public function filterOut($param) {
		// tady k ID nalepíš webalizovaný string
	}
}

Nezapomeň smazat obsah složky temp/cache, až tyto změny uděláš!
Všimni si, že autowiring sám předá ty dva argumenty.

Teď už jen vysvětlit RouterFactory, že to má použít. Když si o tu novou třídu řekneš v konstruktoru, dostaneš ji tam, nebudu se opakovat. Vlastní router psát netřeba, Nette\Application\Routers\Route umí vše, co teď potřebujeme.

$cachedDatabaseRouterHelper = $this->cachedDatabaseRouterHelper;
$router[] = new Route('clanky[/<id [a-z0-9\-]+>]', array (
	'categoryId' => array (
		Route::FILTER_IN => $cachedDatabaseRouterHelper->filterIn, // protože ta třída dědí Nette\Object
		Route::FILTER_OUT => $cachedDatabaseRouterHelper->filterOut
	),
	'action' => 'whateverAction',
	'presenter' => 'whateverPresenter'
));

Doporučuji tu routu realizovat jen podle toho čísla a ten titulek tam mít jen pro okrasu, aby nemusela být jména článků unikátní. Podobně to má např. i idnes, viz např zpravy.idnes.cz/kocka-leze-dirou-pes-oknem-pes-oknem-ffl-/domaci.aspx?c=A130714_113159_domaci_maq . :D

Jinak ten kód jsem psal teď z hlavy, tak k tomu tak prosím přistupuj. Mělo by Tě to ale postrčit správným směrem.

Lumeriol
Generous Backer | 63
+
0
-

Díky za příklad, trochu se tím prokoušu a případně se zas pozeptám.

Snikwah
Člen | 6
+
0
-

Lumeriol: Funguje ti kód, který přidal Lopata (příspěvěk č. 11)?

Mně při použití háže tuto chybu:

Notice

Undefined property: RouterFactory::$myRouterFilter

Poradí mně někdo co s tím?

config.neon

...
	services:
		myRouterFilter: MyCode\Router\MyRouterFilter
...

RouterFactory.php

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

class RouterFactory{
	public function createRouter(){
		$router = new RouteList();
		$routerFilter = $this->myRouterFilter;
...

MyRouterFilter.php

namespace MyCode\Router;

use Nette;

class MyRouterFilter extends Nette\Object {

    protected $connection;

    protected $cache;

    public function __construct(Nette\Database\Connection $db, Nette\Caching\Storages\FileStorage $storage) {
        $this->connection = $db;
        $this->cache = new Nette\Caching\Cache($storage, 'MyRouterFilter');
    }

	public function filterInCategory($param){
		...
	}

    	public function filterOutCategory($param) {
        ...
    }
}

Editoval Snikwah (27. 8. 2013 0:04)

David Matějka
Moderator | 6445
+
0
-

jelikoz v tom kodu v 11 nevidim zadny myRouterFilter, tak predpokladam, ze sis to tam dopsal sam – muzes ukazat cely kod a ladenku?

Snikwah
Člen | 6
+
0
-

Pokusil jsem se použít kód z přednášky Jan Smitka: Routování podrobně a takhle to dopadlo:

RouterFactory.php

class RouterFactory
{

	/**
	 * @return Nette\Application\IRouter
	 */
	public function createRouter(Nette\Database\Connection $db)
	{
		$router = new RouteList();
		$router[] = $frontRouter = new RouteList('Front');
		$frontRouter[] = new Route('kategorie/<id>', array(
							'id' => array (
							  Route::FILTER_IN => function ($id) use ($db) {
							  				if(is_numeric($id)) {
							  					return $id;
							  				} else {
							  					$param = $db->table('category')->where('uri',$id)->fetch()->id;
							  					return $param;
								  			}
							  			},
							  Route::FILTER_OUT => function ($id) use ($db) {
							  				if(!is_numeric($id)) {
							  					return $id;
							  				} else {
							  					$param = $db->table('category')->where('id',$id)->fetch()->uri;
							  					return $param;
								  			}
							  			}
							  ),
							'presenter' => 'Category',
							'action' => 'show'
							));
...

Funguje při zadání /kategorie/<uri> se vyhledá id a pokud někdo zadá /kategorie/<id> automaticky se přepíše adresa na /kategorie/<uri>.

Editoval Snikwah (31. 8. 2013 23:06)

duke
Člen | 650
+
0
-

Snikwah napsal(a):

No service of type FrontModule\Nette\Database\Connection found. Make sure the type hint in Method FrontModule\CategoryPresenter::injectCategory() is written correctly and service of this type is registered.

Z toho je přeci jasné, o co jde… Chybí ti tam:

use Nette;

Alternativně můžeš použít \Nette\Database\Connection (počáteční backslash).