Název článku v URL podle ID

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

Zdravím, začínám s Nette a chci si vytvořit jednoduchý blog. No a samozřejmě chci, aby místo například example.com/clanky/5 byla URL example.com/clanky/nazev-clanku-s-id-5 . Takže…

mám model ArticlesModel a v něm metodu getTitleById

<?php
final class ArticlesModel extends BaseModel
{
  public function getTitleById($id)
  {
    // Tady z databáze tahám název článku, nyní pro jednoduchost např. takhle:
    return $id * 2;
  }
}
?>

a v bootstrap.php mám tuto routu (podle Davida (hier http://jdem.cz/hf3g7) by to mělo jít psát takhle)

<?php
$router[] = new Route('clanky/<id>', array(
  'presenter' => 'Frontend:Articles',
  'action' => 'detail',
  'id' => array(
    'Route::VALUE' => 0, // default value
    'Route::FILTER_OUT' => callback('ArticlesModel::getTitleById'),
  ),
));
?>

A stále mi to vytváří routy ve tvaru example.com/clanky/5 (podle ‚algoritmu‘ zde ukázkové metody getTitleById by měl vracet 10). Prosím tedy o radu, popřípadě jestli na to jdu špatnou cestou, tak o navedení na správnou. Díky

Nette používám ve verzi 1.0-alpha ze dne 17. 9. 2010 pro PHP 5.3; app/temp/ promazávám

Editoval umrlec (23. 9. 2010 11:20)

uestla
Backer | 799
+
0
-

Ahoj, podle Davida to máš až na ty uvozovky kolem klíčů pole – tedy

$router[] = new Route('clanky/<id>', array(
	'presenter' => 'Frontend:Articles',
	'action' => 'detail',
	'id' => array(
		'Route::VALUE' => 0, // default value
		'Route::FILTER_OUT' => callback('ArticlesModel::getTitleById'),
	),
));

zkus nahradit za

$router[] = new Route('clanky/<id>', array(
	'presenter' => 'Frontend:Articles',
	'action' => 'detail',
	'id' => array(
		Route::VALUE => 0, // default value
		Route::FILTER_OUT => callback('ArticlesModel::getTitleById'),
	),
));
umrlec
Člen | 56
+
0
-

@uestla: Díky, dám si facku.

Teď mám zase ale problém s funkcí getTitleById…

<?php
final class ArticleModel extends BaseModel
{
  public function getTitleById($id)
  {
    return $this->connection->fetchSingle('SELECT [article_title] FROM [articles] WHERE [article_id]=%i', $id);
  }
}
?>

ta proměnná connection je z BaseModelu, který vypadá takto:

<?php
abstract class BaseModel extends Object
{
  protected static $connection;

  public static function initializeConnection()
  {
    self::$connection = dibi::connect(Environment::getConfig('database'));
  }

  public function getConnection()
  {
    return self::$connection;
  }
}
?>

a v bootstrap.php mám tedy

<?php
$router[] = new Route('clanky/<id>', array(
  'presenter' => 'Frontend:Articles',
  'action' => 'detail',
  'id' => array(
    Route::VALUE => 0, // default value
    Route::FILTER_OUT => callback('ArticleModel::getTitleById'),
  ),
));
?>

Tohle mi ale hází fatal error „Using $this when not in object context“ v té metodě getTitleById. Dovolím si poprosit o ještě jednu radu.

uestla
Backer | 799
+
0
-

Chyba vše vysvětluje… Jako callback voláš statickou metodu (která ke všemu není uvozená slovíčkem static), která se tváří, že je volána na objektu – ten ovšem není vytvořen.

Čili bych v modelu vytvořil statickou metodu a místo $this->connection->... použil klasické dibi::query(...)

westrem
Člen | 398
+
0
-

To je predsa ale problem, ktory vyplyva z neznalosti PHP a nie nette.

Nastavujes predsa staticky callback ArticleModel::getTitleById a to je potom jasne, ze v tej funkcii nemozes volat $this.

Celkovo ako sa pozeram na ten kod tak pleties staticke veci z nestatickymi.

Prepis ten riadok na toto:

return self::getConnection()->fetchSingle(' ... ');
umrlec
Člen | 56
+
0
-

Tak takhle to funguje, avšak nesedí mi vytváření nového připojení, když už ho jednou vytvářím v BaseModelu, když ale řádek dibi::connect(Nette\Envi… vynechám, tak systém háže DibiException „Dibi is not connected to database.“

<?php
public static function getTitleById($id)
{
  dibi::connect(Nette\Environment::getConfig('database'));
  return String::webalize(dibi::query('SELECT [article_title] FROM [articles] WHERE [article_id]=%i', $id)->fetchSingle());
}
?>
umrlec
Člen | 56
+
0
-

westrem napsal(a):

To je predsa ale problem, ktory vyplyva z neznalosti PHP a nie nette.

Nastavujes predsa staticky callback ArticleModel::getTitleById a to je potom jasne, ze v tej funkcii nemozes volat $this.

Celkovo ako sa pozeram na ten kod tak pleties staticke veci z nestatickymi.

Prepis ten riadok na toto:

return self::getConnection()->fetchSingle(' ... ');
<?php
public static function getTitleById($id)
{
  return String::webalize(self::getConnection()->fetchSingle('SELECT [article_title] FROM [articles] WHERE [article_id]=%i', $id));
}
?>

Fatal Error: Call to a member function fetchSingle() on a non-object …

Editoval umrlec (23. 9. 2010 14:21)

uestla
Backer | 799
+
0
-

Do bootstrapu přidej

Environment::getApplication()->onStartup[] = 'BaseModel::initializeConnection';
umrlec
Člen | 56
+
0
-

uestla napsal(a):

Do bootstrapu přidej

Environment::getApplication()->onStartup[] = 'BaseModel::initializeConnection';

Už mám v kroku 3

$application->onStartup[] = 'BaseModel::initializeConnection';

Editoval umrlec (23. 9. 2010 14:30)

umrlec
Člen | 56
+
0
-

Ale když dám do bootstrapu hned za (nebo místo toho)
$application->onStartup[] = 'BaseModel::initializeConnection';
tohle
dibi::connect(Environment::getConfig('database'));

, tak to jde. :/

westrem
Člen | 398
+
0
-

Trochu sa v tom stracam, aky mas teda aktualny problem? Prosim postni cely kod modelov a najlepsie config.ini a bootstrap.php

umrlec
Člen | 56
+
0
-

Už to jde, nakonec mi poradili jinde, ale řešení vychází z toho Tvého (@westrem). V getConnection jsem ještě musel přidat podmínku, ověřující, zda je proměnná $connection nastavená a pokud ne, vytvoří se spojení a přiřadí se jí. Nevím no proč se mi to spojení neotevře z té metody initializeConnection z $application->onStartup[].

Pak musím také nějak získat zpátky to id článku, takže si nakonec ukládám webalized title do databáze a v routě pak mám ještě filtr FILTER_IN volající metodu getIdByTitle(). Bez toho mi to házelo nějaké errory v detailu článku. Takže dohromady to vypadá takhle:

BaseModel.php

<?php
abstract class BaseModel extends Object
{
	public static $connection = NULL;

	public static function initializeConnection()
	{
		dibi::connect(Environment::getConfig('database'));
	}

	public static function getConnection()
	{
		if(!isset(self::$connection)) {
			self::$connection = dibi::connect(Environment::getConfig('database'));
		}

		return self::$connection;
	}
}
?>

ArticleModel.php

<?php
final class ArticleModel extends BaseModel
{
	public static function getTitleById($id)
	{
		return self::getConnection()->fetchSingle('SELECT [article_title_webalized] FROM [articles] WHERE [article_id]=%i', $id);
	}

	public static function getIdByTitle($title)
	{
		return self::getConnection()->fetchSingle('SELECT [article_id] FROM [articles] WHERE [article_title_webalized]=%s', $title);
	}

}
?>

Routa v bootstrap.php

<?php
$router[] = new Route('clanky/<id>', array(
		'presenter' => 'Frontend:Articles',
		'action' => 'detail',
		'id' => array(
				Route::VALUE => 0, // default value
				Route::FILTER_OUT => callback('ArticleModel::getTitleById'),
				Route::FILTER_IN => callback('ArticleModel::getIdByTitle'),
		),
));
?>
  • v kroku 3
<?php
$application = Environment::getApplication()->onStartup[] = 'BaseModel::initializeConnection';
?>

Editoval umrlec (23. 9. 2010 18:26)

arron
Člen | 464
+
0
-

umrlec napsal(a):

Nevím no proč se mi to spojení neotevře z té metody initializeConnection z $application->onStartup[].

Ono se to spojeni velmi pravdepodobne otevre, ale zhledem k tomu, ze ho nikam nepriradis (treba do te promene self::$connection), tak ho tam pak nenajdes ;-)

redhead
Člen | 1313
+
0
-

umrlec napsal(a):
Nevím no proč se mi to spojení neotevře z té metody initializeConnection z $application->onStartup[].

Ono se otevře, ale už ji nesetuješ do té proměnné $connection, takže se v getConnection vytvoří znova (podmínka není splněna)

Edit: pozdě..

Editoval redhead (23. 9. 2010 18:34)

westrem
Člen | 398
+
0
-

Tak tak ako vravia pani nado mnou, staci to prepisat takto:

public static function initializeConnection()
{
       	self::$connection = dibi::connect(Environment::getConfig('database'));
}
umrlec
Člen | 56
+
0
-

Tak to jsem si asi přepsal, když jsem něco zkoušel, na začátku vlákna to mám, každopádně i když to přiřadím, tak mi to bez té podmínky v getConnection nejde…

arron
Člen | 464
+
0
-

A jsi si jisty, ze se to initializeConnection opravdu zavola?

umrlec
Člen | 56
+
0
-

Nejsem .)

arron
Člen | 464
+
0
-

Ta si do ni dej nejaky dump (nebo vyhozeni vyjimky) nebo tak neco, at si to overis. Pokud se nezavola, tak pak je chyba zase jeste nekde jinde a alespon muzes zacit hledat proc se nevola:-) Takhle nejspis hledame chybu na spatnem miste:-)

umrlec
Člen | 56
+
0
-

Tak asi se ani nespustí, jelikož bez té podmínky žádná změna, to znamená:
Fatal Error: Call to a member function fetchSingle() on a non-object
a s tou podmínkou:
Fu*king Exception #1 : Něco se ti posr***

arron
Člen | 464
+
0
-

Super:-) Tak sem hod svuj bootstrap.php, tusim, ze by mohla byt chyba tam…

umrlec
Člen | 56
+
0
-

Tak dobře tedy:

<?php

use Nette\Debug;
use Nette\Environment;
use Nette\Application\Route;
use Nette\Application\SimpleRouter;


// Step 1: Load Nette Framework
// this allows load Nette Framework classes automatically so that
// you don't have to litter your code with 'require' statements
require LIBS_DIR . '/Nette/loader.php';


// Step 2: Configure environment
// 2a) enable Nette\Debug for better exception and error visualisation
Debug::enable();

// 2b) load configuration from config.ini file
Environment::loadConfig();

// Step 3: Configure application
	// Step 3a: get and setup a front controller
	$application = Environment::getApplication();
	$application->errorPresenter = 'Error';
  // Step 3b: initialize database connection
	$application->onStartup[] = 'BaseModel::initializeConnection';

// Step 4: Setup application router

$router = $application->getRouter();

$router[] = new Route('index.php', array(
	'presenter' => 'Frontend:Homepage',
	'action' => 'default',
), Route::ONE_WAY);

$router[] = new Route('clanky/<id>', array(
		'presenter' => 'Frontend:Articles',
		'action' => 'detail',
		'id' => array(
				Route::VALUE => 0, // default value
				Route::FILTER_OUT => callback('ArticleModel::getTitleById'),
				Route::FILTER_IN => callback('ArticleModel::getIdByTitle'),
		),
));

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



// Step 5: Run the application!
$application->run();

?>

Editoval umrlec (23. 9. 2010 19:48)

arron
Člen | 464
+
0
-

Tak ted uz budu varit z vody:
zkus jenom tak cvicne odstranit u BaseModelu to abstract … jestli to bude mit nejaky vliv. Ale kdyby se to takhle nedalo volat, tak by nejspis Nette zarvalo. Jinak me nic moc nenapada.

Jo a jeste si zmen tu podminku v getConnection, at misto dibi::connect zavola to initializeConnection. Zmizi Ti tam pak ta oskliva duplicita volani dibi::connect :-)

umrlec
Člen | 56
+
0
-

Odstranění abstract jsem zkoušel – nemá vliv. Když volám funkci initializeConnection v té podmínce, tak jako by ta podmínka nebyla, zase fatal error.

arron
Člen | 464
+
0
-

Hele vis co bych zkusil? Vymen v ArticleModel volani self::getConnection() za static::getConnection() a kdyby to nefungovalo, tak jeste v BaseModelu to povymenuj self za static.

umrlec
Člen | 56
+
0
-

Nic … :/

arron
Člen | 464
+
0
-

Tak to uz nevim, ale skoro bych rekl, ze tam bude nejaky problem s temi statickymi funckemi a s tou statickou promenou…a bude to asi pekna haluz:-) Snad to nekdo dalsi objevi…

umrlec
Člen | 56
+
0
-

No jo no, i tak díky za tvůj čas.

redhead
Člen | 1313
+
0
-

self a static je podle mě úplně to samý ne?

marek.dusek
Člen | 99
+
0
-

Ad „je to to samy“:

http://php.net/…bindings.php

redhead
Člen | 1313
+
0
-

Aha! Díky.. K nastudování PHP 5.3 jsem se ještě nedostal.

westrem
Člen | 398
+
0
-

Okej skus to takto:

abstract class BaseModel extends Object
{
        public static $connection = NULL;

        public static function initializeConnection()
        {
                self::$connection = dibi::connect(Environment::getConfig('database'));
        }

        public static function getConnection()
        {
                if (self::$connection === NULL) {
                        self::initializeConnection();
                }

                return self::$connection;
        }
}

Zrus toto:

$application->onStartup[] = 'BaseModel::initializeConnection';

A napis original tohto:

Fu*king Exception #1 : Něco se ti posr***

umrlec
Člen | 56
+
0
-

Takhle s tím trojtým rovnítkem se mi metoda initializeConnection() v metodě getConnection() zavolá.

westrem
Člen | 398
+
0
-

Takze problem vyrieseny?

umrlec
Člen | 56
+
0
-

Vlastně ano, takhle když nevolám initializeConnection() v bootstrapu (což by mi připadalo lepší, ale tak když to nejde, tak to nejde), tak mi příjde v podstatě zbytečná. Takže teď mám BaseModel takhle a šlape to.

<?php
use Nette\Object;
use Nette\Environment;

abstract class BaseModel extends Object
{
  public static $connection = NULL;

  public static function getConnection()
  {
    if (self::$connection === NULL) {
      self::$connection = dibi::connect(Environment::getConfig('database'));
    }

    return self::$connection;
  }
}
?>
westrem
Člen | 398
+
0
-

což by mi připadalo lepší, ale tak když to nejde, tak to nejde

Priznam sa, ze vynimocne sa mi nechcelo patrat po tom, preco ti to v boot-e neslo, istotne by to bola podla mna ale nejaka trivialita.

To, ze to bolo v boot-e neznamena nutne, ze to bolo lepsie. Takto to mas pekne lazy-load a ak by v buducnu prisiel nejaky poziadavok, ktory vobec nevyuziva BaseModel a DB tak sa k nej ani nepripajas a nenavazujes zbytocny connection – rychlejsie spracovanie poziadavku ;)

umrlec
Člen | 56
+
0
-

Tak jo :) tak tedy díky Ti i všem ostatním, kteří se mi pokoušeli pomoct.