Router vracia objekty / entity namiesto skalarov

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

Pouziva niekto router tak ze mu po spracovani url vitiahne uz aj spravne netity a nie len IDcka ?

napr url: „/liptov/chaty“ router spracuje a vrati parametre kde budu dve entity, jedna pre „liprov“ a druha pre „chaty“, a nevrati len obycajne idcka tych entity.

potom si v presenteroch nemusim vytahovat entity (ani injectovat zbytocne repositare) lebo parametre uz su entity:

<?php

class FooPresenter extends BasePresenter
{
	public function actionBar($region, $typZariadenie)
	{
		// $region instanceof \Entity\Region
		// $typZariadenie instanceof \Entity\RentalType
	}
}
?>

Editoval achtan (8. 2. 2013 8:32)

frosty22
Člen | 373
+
0
-

Vypadá to zajímavě, leč možná bych viděl problém ve dvou věcech:

  1. Zbytečné query navíc? Nemusíš vždy potřebovat ony entity parametrů, viz příklad kdy například potom ihned přesměrováváš jinam.
  2. Typová kontrola – dokázal bych si představit, že budu chtít provádět typovou kontrolu, avšak co se stane když bude invalidní ID parametru? Potom nebude nalezena entita, a typová kontrola skončí chybou „Fatal Error: Argument 1 must be an instance of \Entity\Region“, ikdyž asi by se holt musel parametr vždy uvádět jako nepovinný
  3. To už nevidím jako problém rozhraní, ale spíše implementace a to, jak vybírat které parametry převádět na entity a které ne? Toto by se asi nemohlo plně automatizovat.
enumag
Člen | 2118
+
0
-

@frosty22:

  1. Neumím si dost dobře přestavit situaci kdy bych přesměrovával aniž bych se na tu entitu vůbec podíval. Přesměrování obvykle závisí na nějaké podmínce.
  2. Řešením je, že router danou URL adresu jednoduše odmítne. Pak máš parametry povinné a klidně i s typovou kontrolou.
  3. Dá, ne sice úplně ale to bych stejně považoval za nežádoucí.
frosty22
Člen | 373
+
0
-

@enumag:

  1. Tak to je asi pravda, ikdyž asi projdu jeden projekt a podívám se, zda-li někde nepoužívám následně entity z parametrů, ale líbí se mi to.
  2. Toto je vlastně pravda, to jsem si neuvědomil.
enumag
Člen | 2118
+
0
-

@Felix: Jo… tohle jsem používal před víc jak půlrokem. Fuj. :-) Nutí tě to psát samostatnou routu pro každý presenter a navíc vyžaduje úpravu Nette.

Editoval enumag (9. 2. 2013 12:36)

frosty22
Člen | 373
+
0
-

Přišel již někdo na nějaké efektivní řešení? Taky jsem si s tím trošku hrál, ale bohužel jsem pohořel. Nechtěl jsem upravovat nette, takže jsem šel cestou, kdy ve startupu jsem si zavolal svůj objekt, kterému jsem předal presenter, ten zjistil jaká action a render metoda se volá a přes reflection jsem si přečetl typehinting těchto metod a získal si patřičnou entitu dle primárního klíče.

Následně jsem přehodil parametry presenteru, sice tohle funguje, avšak následně to končí problémem, kdy si nette sahá pro parametry, aby zpětně vytvářelo request, a objekty se mu nelíbí.

Napadlo mě ještě tedy řešení, kdy přepíši metodu run(), která by toto řešila a pouze zavolala action a render metody s entitami a neměnila parametry presenteru. Což je jistě bezpečenější řešení než hledat, kde všude nette parametry presenteru potřebuje. Jen se mi moc nelíbí, že run() je trošku delší kus kódu, který bych musel v podstatě copy&paste a jen doplnit volání s entity.

frosty22
Člen | 373
+
0
-

Heuréka, tak již jsem našel snad řešení – zatím tedy BETA verze :) Ještě jsem to pořádně neotestoval a možná by se hodilo to cachovat, ale funguje a zase tak extrémní prasárna to není .. do BasePresenteru stačí tedy přidat (a přepsat tím metodu tryCall):

<?php

	/**
	* @var \Doctrine\ORM\EntityManager
	*/
	protected $entityManager;

	/**
	 * Calls public method if exists.
	 * @param  string
	 * @param  array
	 * @return bool  does method exist?
	 */
	protected function tryCall($method, array $params)
	{
		$rc = $this->getReflection();
		if ($rc->hasMethod($method)) {
			$rm = $rc->getMethod($method);
			if ($rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic()) {
				$this->checkRequirements($rm);
				$args = $rc->combineArgs($rm, $params);

				if (\Nette\Utils\Strings::match($method, "~^(action|render|handle).+~")) {

					$methodParams = $rm->getParameters();
					foreach ($methodParams as $i => $param) {
						/** @var \Nette\Reflection\Parameter $param */
						if ($className = $param->getClassName()) {
							$paramName = $param->getName();

							if ($paramValue = $args[$i]) {
								$entity = $this->findById($className, $paramValue);
								if ($entity) {
									$args[$i] = $entity;
								} else {
									throw new \Nette\Application\BadRequestException("Value '$paramValue' not found in collection '$className'.");
								}
							} else {
								if (!$param->allowsNull()) {
									throw new \Nette\Application\BadRequestException("Value '$param' cannot be NULL.");
								}
							}

						}
					}

				}

				$rm->invokeArgs($this, $args);
				return TRUE;
			}
		}

		return FALSE;
	}


	/**
	 * Find entity by ID.
	 * @param string $entityName
	 * @param int $id
	 * @return object|null
	 */
	protected function findById($entityName, $id)
	{
		return $this->entityManager->find($entityName, $id);
	}


?>

A poté pokud chcete v presenterech přijímat entitu namísto skaláru, stačí jen definovat typovou kontrolu (umí samozřejmě i NULL):

<?php

class TestPresenter extends \App\BasePresenter
{

	public function actionDefault(\Entities\User $foo, \Entities\Shop $shop = NULL)
	{
		...
	}

	public function renderDefault(\Entities\User $foo)
	{
		...
	}

}

?>

Mělo by to předávat tedy jak render*, action* i handle* metodám v případě, že definujete typovou kontrolu. A v případě nenalezené entity, tak vyhodí BadRequestException případně pokud se předá prázná hodnota a je povolený NULL (viz výše argument $shop), tak to projde.

akadlec
Člen | 1326
+
0
-

Supr, dnes vyzkoušeno a fachčí, aspoň mi odpadá loadování entit a následné vyhazování BadRequestException

David Ďurika
Člen | 328
+
0
-

zaujimave, ja to sice robim tam ze us samotny router vracia a prijima entity, ale ani toto nieje zle…

Tomáš Votruba
Moderator | 1114
+
+1
-

Výše uvedený přístup jsem implementoval do balíčku Zenify/DoctrineMethodsHydrator.