Form::add{Component}

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

Trošku se mi nepozdává prototypování Form třídy jednotlivýma komponentama z extras (navíc neprototypujou stejným způsobem), tak jsem si udělal potomka AppForm, který z MyForm::add{Component}(…) vytvoří danou komponentu, v případě že taková třída s rozhraním IComponent existuje.

Jinak tady v těch call_user_func apod. si nejsem uplně jistej, takže možná/určitě bude nějaké elegantnější řešení.

Za přípomínky budu rád. Případná implementace přímo do Nette by byla pěkná, ale tohle už by asi Framework řešit neměl.

/**
*
* @author Viliam Kopecký
* @version 0.8
* @copyright 2009
*/

class MyForm extends AppForm {
	/**
	* Call to undefined method.
	* Cares of not-native FormControls
	*
	* @param string $ method name
	* @param array $ arguments
	* @return mixed
	* @throws Exception
	*/
	public function __call($name, $args)
	{
		if (String::startsWith($name, "add") && class_exists($class = substr($name, 3))) {
			$name = array_shift($args);
			$component = @new $class;
			if ($component instanceof IComponent) {
				call_user_func_array(array($component, "__construct"), $args);
				$this->addComponent($component, $name);
				return $this[$name];
			} else {
				throw new Exception("'$class' must implement 'IComponent'");
			}
		}
		return parent::__call($name, $args);
	}
}

Editoval enoice (24. 6. 2009 21:01)

PetrP
Člen | 587
+
0
-

Huh, call_user_func_array(array($component, "__construct"), $args); je nesmyl, protože construct se volá při vytváření instance, taky nechápu proč potlačuješ chyby v @new $class;

Ale hlavně je hloupost vytvařet instanci když není instanceof IComponent.
Dělal bych to možná nějak takto:

if (in_array('IComponent',class_implements($class)))
{
	$component = new $class;
}

Taky proč toto: $name = array_shift($args);?

// EDIT
taky by hlavně mělo v případě Formsů implementovat IFormControl a né jen IComponent

Editoval PetrP (25. 6. 2009 14:27)

ViliamKopecky
Nette hipster | 230
+
0
-

Jo, máš pravdu, ale stále… Jak předat parametry constructoru?

„If eval() is the answer, you're almost certainly asking the wrong question.“ – Rasmus Lerdorf

jasir
Člen | 746
+
0
-

enoice napsal(a):

Jo, máš pravdu, ale stále… Jak předat parametry constructoru?

„If eval() is the answer, you're almost certainly asking the wrong question.“ – Rasmus Lerdorf

<?php
$a = new $class(arguments);
?>
ViliamKopecky
Nette hipster | 230
+
0
-

jasir napsal(a):

enoice napsal(a):

Jo, máš pravdu, ale stále… Jak předat parametry constructoru?

„If eval() is the answer, you're almost certainly asking the wrong question.“ – Rasmus Lerdorf

<?php
$a = new $class(arguments);
?>

No… Máš argumenty v poli a potřebuješ je předat do constructoru. Pro normální funkci nebo metodu se využije call_user_func.

<?php

// například pro RadioList
$class = "RadioList";
$args = array("rl", "Vyberte možnost", array("option 1", "option 2"));

$object = new $class(- sem dostat $args -);

?>

Editoval enoice (25. 6. 2009 16:04)

zaxeeq
Člen | 17
+
0
-

Já použil něco jako

	private static $INSTANCE_CACHE = array();

	public static function createInstance($name, $args = NULL) {
		$count = count($args);

		switch($count) {
			case 0:
				return new $name();

			case 1:
				return new $name($args[0]);

			case 2:
				return new $name($args[0], $args[1]);

			case 3:
				return new $name($args[0], $args[1], $args[2]);
		}

		//more than three parameters

		if (isset(self::$INSTANCE_CACHE[$name])) {
			$classObj = self::$INSTANCE_CACHE[$name];
		} else {
			$classObj = new ReflectionClass($name);
			self::$INSTANCE_CACHE[$name] = $classObj;
		}

		return $classObj->newInstanceArgs($args);
	}
ViliamKopecky
Nette hipster | 230
+
0
-

Tak jsme zase objevil nějaké zákoutí PHP. A myslím, že tohle je docela čisté řešení.

<?php

/**
*
* @author Viliam Kopecký
* @version 0.9
* @copyright 2009
*/

class MyForm extends AppForm {
	/**
	* Call to undefined method.
	* Cares of not-native FormControls
	*
	* @param string $ method name
	* @param array $ arguments
	* @return mixed
	* @throws Exception
	*/
	public function __call($method_name, $args)
	{
		if (String::startsWith($method_name, "add") && class_exists($class_name = substr($method_name, 3))) {
			$name = array_shift($args);
			if (in_array('IFormControl', class_implements($class_name))) {
				$class = new ReflectionClass($class_name);
				$instance = $class->newInstanceArgs($args);
				$this->addComponent($instance, $name);
				return $this[$name];
			}
		}
		return parent::__call($method_name, $args);
	}
}

?>
ViliamKopecky
Nette hipster | 230
+
0
-

zaxeeq napsal(a):

Já použil něco jako

Vypadá to zvláštně, ale asi je to v případě switche rychlejší, než vytvářet ReflectionClass. Hmmm

zaxeeq
Člen | 17
+
0
-

enoice napsal(a):

zaxeeq napsal(a):

Já použil něco jako

Vypadá to zvláštně, ale asi je to v případě switche rychlejší, než vytvářet ReflectionClass. Hmmm

Vycházel jsem z http://blog.liip.ch/…is-slow.html a zkusil benchmark a ReflectionClass::newInstanceArgs je skutečně pomalejší (vyšlo mě to cca o 30%)

ViliamKopecky
Nette hipster | 230
+
0
-

aha, ta $instance_cache je také chytrá. Zakomponuju to tam, díky

zaxeeq
Člen | 17
+
0
-

enoice napsal(a):

aha, ta $instance_cache je také chytrá. Zakomponuju to tam, díky

Kdyžtak to nějak rozumně pojmenuj, vlastně tam nejsou instance ale reflection objekty ;)

ViliamKopecky
Nette hipster | 230
+
0
-
class MyForm extends AppForm {
	private static $reflections = array();
	/**
	* Call to undefined method.
	* Cares of non-native FormControls
	*
	* @param string $ method name
	* @param array $ arguments
	* @return mixed
	* @throws Exception
	*/
	public function __call($method_name, $args)
	{
		if (String::startsWith($method_name, "add") && class_exists($class_name = substr($method_name, 3))) {
			$name = array_shift($args);
			if (in_array('IFormControl', class_implements($class_name))) {
				if (($count = count($args)) <= 3) {
					switch ($count) {
						case 1:
							$this->addComponent(new $class_name($args[0]), $name);
							break;
						case 2:
							$this->addComponent(new $class_name($args[0], $args[1]), $name);
							break;
						case 3:
							$this->addComponent(new $class_name($args[0], $args[1], $args[2]), $name);
							break;
					}
					return $this[$name];
				}
				if (!isset(self::$reflections[$class_name])) {
					self::$reflections[$class_name] = new ReflectionClass($class_name);
				}
				$instance = self::$reflections[$class_name]->newInstanceArgs($args);
				$this->addComponent($instance, $name);

				return $this[$name];
			}
		}
		return parent::__call($method_name, $args);
	}
}
David Grudl
Nette Core | 8117
+
0
-

Switch je megaprasárna ;)

Tohle se dá asi skutečně řešit jen přes $class->newInstanceArgs($args), což je ale pěkné a čisté. Když už se vytváří reflexe třídy, doporučil bych nahradit class_implements za $class->implementsInterface('IFormControl'). A klidně i $class->isInstantiable(), ať se odfiltrují abstraktní třídy.

zaxeeq
Člen | 17
+
0
-

David Grudl napsal(a):

Switch je megaprasárna ;)

nj, ale reflexe je zase pomalá ;)

ViliamKopecky
Nette hipster | 230
+
0
-

@Dave: Takže košér takhle?

	public function __call($method_name, $args)
	{
		if (String::startsWith($method_name, "add") && class_exists($class_name = substr($method_name, 3))) {
			$name = array_shift($args);
			if (!isset(self::$reflections[$class_name])) {
				self::$reflections[$class_name] = new ReflectionClass($class_name);
			}
			$class = &self::$reflections[$class_name];
			if ($class->implementsInterface('IFormControl') && $class->isInstantiable()) {
				$instance = $class->newInstanceArgs($args);
				$this->addComponent($instance, $name);
				return $this[$name];
			}
		}
		return parent::__call($method_name, $args);
	}
David Grudl
Nette Core | 8117
+
0
-

Jo, to je super.

(osobně bych asi reflexe nekešoval, ale to je fuk)

Důvod, proč něco takového není ve frameworku, jsou namespaces. Ve kterých hledat?

Patrik Votoček
Člen | 2221
+
0
-

Nastavitelné pole s kde by byly ony namespace? Defaultně by tam bylo jenom „Nette\Form“ …? Nebo v názvu metody (typu fomsojidní „componenty“) používat znak/řetězec který nahradí \ pokud tam tohle nebude tak se použije klasické „Nette\Form“… Je to jenom nápad…

ViliamKopecky
Nette hipster | 230
+
0
-

Nojo, na namespaces jsem nemyslel, přitom už se nám (možná) brzy finálně objeví.