Název článku/postu v URL z databáze

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

Zdravím,

dělám aplikaci v Nette a potřeboval bych, aby v URL localhost/homepage/detail/0 se místo ID zobrazil název (title) postu/článku z databáze.
Projížděl jsem fórum i dokumentaci a vyčetl jsem, že se to dělá pomocí FILTER_IN a FILTER_OUT.
Zkoušel jsem všechno, ale zatím neúspěšně.

V modelu mám vytvořenou statickou funkci, která má vzít název podle id.

<?php
namespace Model;

class AdRepository extends Repository {

	public function getAll() {
		return $this->database->table('ad');
	}

	public function getDetail($id) {
		return $this->database->table('ad')->where('idAd = ?', $id);
	}

	public function getIdByUrl() {

	}

	public static function getTitleById() {
		//zde se má vracet title podle idAd
	}
}
?>
Pavel Macháň
Člen | 282
+
0
-

Checkpoint napsal(a):

Zdravím,

dělám aplikaci v Nette a potřeboval bych, aby v URL localhost/homepage/detail/0 se místo ID zobrazil název (title) postu/článku z databáze.
Projížděl jsem fórum i dokumentaci a vyčetl jsem, že se to dělá pomocí FILTER_IN a FILTER_OUT.
Zkoušel jsem všechno, ale zatím neúspěšně.

V modelu mám vytvořenou statickou funkci, která má vzít název podle id.

<?php
namespace Model;

class AdRepository extends Repository {

	public function getAll() {
		return $this->database->table('ad');
	}

	public function getDetail($id) {
		return $this->database->table('ad')->where('idAd = ?', $id);
	}

	public function getIdByUrl() {

	}

	public static function getTitleById() {
		//zde se má vracet title podle idAd
	}
}
?>

Statiku z toho vynech. Do router factory si injectni pomocí konstruktoru tento model.
A jak funguje FILTER_IN | FILTER_OUT je dobře vysvětleno v Plannete

Checkpoint
Člen | 34
+
0
-

Model AdRepository.php

<?php
namespace Model;

class AdRepository extends Repository {

	public function getAll() {
		return $this->database->table('ad');
	}

	public function getDetail($id) {
		return $this->database->table('ad')->where('idAd = ?', $id);
	}

	public function getIdByUrl() {

	}

	public function getTitleById($id) {
		return $this->database->query('SELECT title FROM ad WHERE idAd = ?', $id); //získání title
	}
}
?>

RouterFactory.php

<?php

namespace App;

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


/**
 * Router factory.
 */
class RouterFactory
{

	public $adRepository;

	public function inject(Model\AdRepository $adRepository) {
		$this->adRepository = $adRepository;
	}

	/**
	 * @return \Nette\Application\IRouter
	 */
	public function createRouter()
	{
		$router = new RouteList();
		$router[] = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
		$router[] = new Route('<presenter>/<action>/<id>', array(
		  'presenter' => 'Homepage',
		  'action' => 'detail',
		  'id' => array(
		    'Route::VALUE' => 0, // default value
		    'Route::FILTER_OUT' => callback('adRepository::getTitleById'),
		  ),
		));
		return $router;
	}

}
?>

Nic se neděje. URL pořád ve tvaru localhost/homepage/detail/0.

jiri.pudil
Nette Blogger | 1029
+
0
-
  1. Ten callback stejně vytváříš statický. Musíš ho navázat na tu instanci, kterou si injektuješ.
  2. Route::VALUE a Route::FILTER_OUT jsou konstanty třídy Route. Apostrofy okolo nich nemají co dělat.

Ergo nějak takhle by to mělo fungovat:

'id' => array(
	Route::VALUE => 0,
	Route::FILTER_OUT => array($this->adRepository, 'getTitleById'),
),

A jedno doporučení na závěr: v čemkoliv, co není presenter, je nejvhodnější použít injektování přes konstruktor, tzn. metodu inject přejmenuj na __construct (a proměnná $adRepository by měla být private).

Checkpoint
Člen | 34
+
0
-

jiri.pudil napsal(a):

  1. Ten callback stejně vytváříš statický. Musíš ho navázat na tu instanci, kterou si injektuješ.
  2. Route::VALUE a Route::FILTER_OUT jsou konstanty třídy Route. Apostrofy okolo nich nemají co dělat.

Ergo nějak takhle by to mělo fungovat:

'id' => array(
	Route::VALUE => 0,
	Route::FILTER_OUT => array($this->adRepository, 'getTitleById'),
),

A jedno doporučení na závěr: v čemkoliv, co není presenter, je nejvhodnější použít injektování přes konstruktor, tzn. metodu inject přejmenuj na __construct (a proměnná $adRepository by měla být private).

Děkuji za rychlý feedback a tipy, ale teď mi zase vyhazuje laděnka chybu.

Pokud nechám metodu inject(), tak laděnka vyhodí error.

call_user_func() expects parameter 1 to be a valid callback, first array member is not a valid class name or object

Pokud použiji metodu __construct, tak vyhodí zase jiný error.

Nette\DI\ServiceCreationException

Service ‚22_App_RouterFactory‘: No service of type App\Model\AdRepository found. Make sure the type hint in App\RouterFactory::__construct() is written correctly and service of this type is registered.

Přitom službu Model\AdRepository mám zaregistrovanou v config.neon.

Pavel Macháň
Člen | 282
+
0
-

Checkpoint napsal(a):

Děkuji za rychlý feedback a tipy, ale teď mi zase vyhazuje laděnka chybu.

Pokud nechám metodu inject(), tak laděnka vyhodí error.

call_user_func() expects parameter 1 to be a valid callback, first array member is not a valid class name or object

Pokud použiji metodu __construct, tak vyhodí zase jiný error.

Nette\DI\ServiceCreationException

Service ‚22_App_RouterFactory‘: No service of type App\Model\AdRepository found. Make sure the type hint in App\RouterFactory::__construct() is written correctly and service of this type is registered.

Přitom službu Model\AdRepository mám zaregistrovanou v config.neon.

Dej si pozor na Namespace… v erroru to máš i napsaný
Router má namespace App; takže pokud napíšeš

namespace App;
__construct(Model\AdRepository $repository) // = App\Model\AdRepository

// musí to být
__construct(\Model\AdRepository $repository) // = Model\AdRepository

ale ty to máš mít jen Model\AdRepository takže musíš uvést \Model\AdRepository … to \ je tam důležité

Pokud nemáš na začátku lomítko tak se to automaticky bere dle namespace které je uvedeno. Pokud uvedeš lomítko, řekneš tomu, že se jede od „začátku“. Pokud bys chtěl uvést bez lomítka aby to šlo musel bys použít use Model;

namespace App;
use Model;
__construct(Model\AdRepository $repository) // = Model\AdRepository

Editoval EIFEL (9. 2. 2014 14:15)

Checkpoint
Člen | 34
+
0
-

EIFEL napsal(a):

Checkpoint napsal(a):

jiri.pudil napsal(a):

  1. Ten callback stejně vytváříš statický. Musíš ho navázat na tu instanci, kterou si injektuješ.
  2. Route::VALUE a Route::FILTER_OUT jsou konstanty třídy Route. Apostrofy okolo nich nemají co dělat.

Ergo nějak takhle by to mělo fungovat:

'id' => array(
	Route::VALUE => 0,
	Route::FILTER_OUT => array($this->adRepository, 'getTitleById'),
),

A jedno doporučení na závěr: v čemkoliv, co není presenter, je nejvhodnější použít injektování přes konstruktor, tzn. metodu inject přejmenuj na __construct (a proměnná $adRepository by měla být private).

Děkuji za rychlý feedback a tipy, ale teď mi zase vyhazuje laděnka chybu.

Pokud nechám metodu inject(), tak laděnka vyhodí error.

call_user_func() expects parameter 1 to be a valid callback, first array member is not a valid class name or object

Pokud použiji metodu __construct, tak vyhodí zase jiný error.

Nette\DI\ServiceCreationException

Service ‚22_App_RouterFactory‘: No service of type App\Model\AdRepository found. Make sure the type hint in App\RouterFactory::__construct() is written correctly and service of this type is registered.

Přitom službu Model\AdRepository mám zaregistrovanou v config.neon.

Dej si pozor na Namespace… v erroru to máš i napsaný
Router má namespace App; takže pokud napíšeš

namespace App;
__construct(Model\AdRepository $repository) // = App\Model\AdRepository

ale ty to máš mít jen Model\AdRepository takže musíš uvést \Model\AdRepository … to \ je tam důležité

Už jsem na to došel, akorát jsem chtěl upravit předchozí příspěvěk. :)
Každopádně neustále přetrvává první chybka. :X

jiri.pudil
Nette Blogger | 1029
+
0
-

Co je první chybka? Neplatný callback? V tom případě musíš injektovat přes konstruktor (resp. jsou i jiné způsoby, ale tenhle je nejjednodušší a nejčistší).

Checkpoint
Člen | 34
+
0
-

Tak jsem teda dal na Vaši radu a injektoval jsem přes konstruktor.
Všechno funguje, ale pořád je URL ve stejném tvaru. Po názvu postu z DB ani stopy.

jiri.pudil
Nette Blogger | 1029
+
0
-

Všechno funguje, ale pořád je URL ve stejném tvaru. Po názvu postu z DB ani stopy.

Máš tam dvě de facto stejné routy, takže se ti matchne hned ta první a ke druhé, kde máš filter funkci, už to vůbec nedojde.

Pavel Macháň
Člen | 282
+
0
-

Checkpoint napsal(a):

Tak jsem teda dal na Vaši radu a injektoval jsem přes konstruktor.
Všechno funguje, ale pořád je URL ve stejném tvaru. Po názvu postu z DB ani stopy.

Routy se čtou v pořadí jak je tam vkládáš. To znamená, že se veme první routa která vyhovuje a další se už ignorují (v debug panelu jsou označeny: may).

Nejdřív si dej na první místo tvoje specifické routy a všeobecné dej až na konec jako poslední možnost.

Checkpoint
Člen | 34
+
0
-

Za lomítkem se nezobrazí nic. Tedy adresa je ve tvaru localhost/inzerat/detail-inzeratu/

<?php
public function createRouter()
	{
		$router = new RouteList();
		$router[] = new Route('inzerat/detail-inzeratu/<id>', array(
		  'presenter' => 'Homepage',
		  'action' => 'detail',
		  'id' => array(
    			Route::VALUE => 0,
   				Route::FILTER_OUT => array($this->adRepository, 'getTitleById'),
			),
		));
		$router[] = new Route('[<presenter>/]<action>[/<id>]', 'Homepage:default');
		return $router;
	}
?>
Checkpoint
Člen | 34
+
0
-

Tak jsem se s tím chvíli hrál, až jsem se dostal do fáze, kdy mi laděnka vypisuje

rawurldecode() expects parameter 1 to be string, object given

Funkce rawurldecode() očekává parametr 1 jako string, ale já dodávám objekt? wtf

David Matějka
Moderator | 6445
+
0
-

nevim, s cim vsim sis hral a jak je tedy kod, ktery si posilal aktualni, ale

public function getTitleById($id) {
        return $this->database->query('SELECT title FROM ad WHERE idAd = ?', $id); //získání title
    }

nevraci title, ale ResultSet, zkus za to pridat ->fetch()->title

Twista
Člen | 48
+
0
-

Je to protože tvoje repository vraci ResultSet (to co vrací query), musíš v té metodě vrátit string :) tzn. uprav metodu getTitleById()

Checkpoint
Člen | 34
+
0
-

Tak jsem tedy zkusil přidat ->fetch()->title.

Skončí to u erroru Trying to get property of non-object.

David Matějka
Moderator | 6445
+
0
-

asi tam posilas id, ktere neexistuje. dej si tam nejakou kontrolu

Checkpoint
Člen | 34
+
0
-

Laděnka mi ale vyhazuje error, když chci přistupovat ke sloupci v jiné tabulce.
Tohle je červeně označeno.

<a>Lokalita – <?php echo Nette\Templating\Helpers::escapeHtml($posledni->locality->title, ENT_NOQUOTES) ?></a>

Pokud metodu getTitleByUrl() nezavolám, tak všechno funguje v pořádku.

MartinitCZ
Člen | 580
+
0
-

@**Checkpoint**: Po query() nemůžeš mít ->fetch().
Pořádně si přečti https://doc.nette.org/cs/database

David Matějka
Moderator | 6445
+
0
-

@martinit: query vraci ResultSet, nad kterym fetch volat muzes

@Checkpoint: hod sem ladenku

Checkpoint
Člen | 34
+
0
-

Zde je screen laděnky.
https://www.dropbox.com/…r_nette_.jpg

Přikládám ještě zdrojové kódy

AdRepository.php

<?php
namespace Model;

class AdRepository extends Repository {

	public function getAll() {
		return $this->database->table('ad');
	}

	public function getDetail($id) {
		return $this->database->table('ad')->where('idAd = ?', $id);
	}

	public function getIdByUrl() {

	}

	public function getTitleById($id) {
		return $this->database->query('SELECT slug FROM ad WHERE idAd = ?', $id)->fetch()->slug;
	}
}
?>

RouterFactory.php

<?php

namespace App;
use Model;
use Nette,
	Nette\Application\Routers\RouteList,
	Nette\Application\Routers\Route,
	Nette\Application\Routers\SimpleRouter;


/**
 * Router factory.
 */
class RouterFactory
{

	private $adRepository;

	public function __construct(Model\AdRepository $adRepository) {
		$this->adRepository = $adRepository;
	}

	/**
	 * @return \Nette\Application\IRouter
	 */
	public function createRouter()
	{
		$router = new RouteList();
		$router[] = new Route('vsechny-obory/', array(
			'presenter' => 'Obor',
			'action' => 'default',
			'id' => NULL,
		));
		$router[] = new Route('vsechny-obory/obor/<id>', array(
			'presenter' => 'Obor',
			'action' => 'detail',
			'id' => NULL,
		));

		$router[] = new Route('inzerat/detail-inzeratu/<id>', array(
			'presenter' => 'Homepage',
			'action' => 'detail',
			'id' => array(
				Route::VALUE => 0,
				Route::FILTER_OUT => array($this->adRepository, 'getTitleById'),
			),
		));

		$router[] = new Route('[<presenter>/]<action>[/<id>]', 'Homepage:default');
		return $router;
	}

}
?>

Editoval Checkpoint (11. 2. 2014 11:04)

David Matějka
Moderator | 6445
+
0
-

a existuje zaznam s tim id?
dej si tam kontrolu, jak jsem ti rikal.

public function getTitleById($id) {
	$query = $this->database->query('SELECT slug FROM ad WHERE idAd = ?', $id);
	if($row = $query->fetch()) {
		return $row->slug;
	}
	return NULL;
}
Checkpoint
Člen | 34
+
0
-

V odkazu už se ukazuje titulek, super.

Ale snaží se přejít na DetailPresenter, ne na akci ‚detail‘.

https://www.dropbox.com/…r_action.jpg

David Matějka
Moderator | 6445
+
0
-
  1. pravdepodobne budes chtit do presenteru prijmout ID jako cislo, ne? dej si tam tedy i FILTER_IN, ktery prelozi slug na id
  2. na jakou url se to dotazovalo?
Checkpoint
Člen | 34
+
0
-

Zde je URL.
<a n:href=„Homepage:detail $posledni[‚idAd‘]“>

David Matějka
Moderator | 6445
+
0
-

a vygenerovana url?

Checkpoint
Člen | 34
+
+1
-

Už to frčí, super.
Pro zajímavost, kdyby někdo hledal, tak jsem vytvořil metodu getIdByURL().
A pomocí FILTER_IN přeložil Slug na ID.

Zde je metoda v AdRepository.php

<?php
public function getIdByUrl($slug) {
$query = $this->database->query('SELECT idAd FROM ad WHERE slug = ?', $slug);
if($row = $query->fetch()) {
	return $row->idAd;
}
    	return NULL;
}
?>

Zde je routa v RouterFactory.php

<?php
...
$router[] = new Route('inzerat/detail-inzeratu/<id>', array(
	'presenter' => 'Homepage',
	'action' => 'detail',
	'id' => array(
		Route::FILTER_IN => array($this->adRepository, 'getIdByUrl'),
		Route::FILTER_OUT => array($this->adRepository, 'getTitleById'),
	),
));

...
?>

Díky všem zůčastněným za pomoc. :)