Rozšíření Object o metodu factory()

- Milo
- Nette Core | 1283
Často u objektů používám statickou metodu factory, abych mohl při vytváření objektu rovnou použít fluent. A jelikož už jsem si na to tak zvyknul, napadlo mě, jestli o tuto funkčnost nerozšířit i Nette.
namespace Nette;
abstract class ObjectFactory extends Object
{
public static function factory()
{
$class = get_called_class();
$ref = new \ReflectionClass($class);
if (!$ref->hasMethod('__construct')) {
return new $class;
}
$args = func_get_args();
return $ref->newInstanceArgs($args);
}
}
class MyClass extends ObjectFactory
{
}
$o = MyClass::factory()->fluent();
Chybou krásy je potřeba php 5.3. U php 5.2 se dá
get_called_class() nahradit snad jen debug_backtrace()
varinatou.

- Filip Procházka
- Moderator | 4668
Docela používané je newInstance().
Editoval HosipLan (2. 11. 2011 8:04)

- Honza Marek
- Člen | 1664
Myslim, že v Nette\Objectu by mělo být co nejméně funkčnosti. Stačilo by mi, kdyby dělal jen takové ty věci jako vyhazování výjimek při přístupu k neinicializované property a podobně. Pokud někdo potřebuje tuhle metodu (a nepopírám její jistou užitečnost), nechť si Nette\Object podědí a používá ten.

- Filip Procházka
- Moderator | 4668
Extension method je dobrý nápad, metodu by totiž obsahovaly i Nette třídy. Osobně si myslím, že je to jenom „zbytečný syntactic sugar“, naštěstí nijak nepřekáží.

- Milo
- Nette Core | 1283
S čistotou Nette\Objectu souhlasím. Pokud by se statická metoda přidala
přímo jako Object::construct() (varianta newInstance() taky
pěkná), nelze ji v potomkovi předefinovat jako nestatickou. Pokud by ji
někdo v potomkovi předefinoval jako statickou s jiným počtem povinných
parametrů, dostane E_STRICT.
extensionMethod() použít nejde, metoda se pak nedá volat
staticky. Navíc get_called_class() lze volat jen v určitých
případech.
Měl jsem spíš na mysli přidat novou třídu, která by tuto funkčnost
poskytovala a rozšiřovala Nette\Object. Ostatně tak to dělám
teď já.
namespace Nette;
abstract class ObjectFactory extends Object // název třídy berte s rezervou, to mi fakt nejde
{
public static function construct()
{
$class = get_called_class();
$ref = new \ReflectionClass($class);
if (!$ref->hasMethod('__construct')) {
return new $class;
}
$args = func_get_args();
return $ref->newInstanceArgs($args);
}
}
Editoval Milo (2. 11. 2011 18:02)

- Patrik Votoček
- Člen | 2221
Milo napsal(a):
Navíc
get_called_class()lze volat jen v určitých případech.
Why?

- Milo
- Nette Core | 1283
To jsem blbě napsal. Funkci get_called_class() nelze volat
z extension metody tak, aby vrátila název třídy ke které je extension
metoda přidaná. Extension metoda by musela nějak zjišťovat, ke které
třídě je přidaná. Příklad:
class MyClass extends Nette\Object
{
}
function construct()
{
// Jak tady zjistit, že jsem na třídě MyClass abych mohl vrátit instanci MyClass?
}
MyClass::extensionMethod('construct', 'construct');
// Ale extension metodu teď nelze volat staticky, takže to je stejně jedno
$o = MyClass::construct()->fluent(); // nelze

- Milo
- Nette Core | 1283
HosipLan napsal(a):
Osobně si myslím, že je to jenom „zbytečný syntactic sugar“, naštěstí nijak nepřekáží.
Ještě k tomuto. Zbytečný mi zas tak nepřijde, hezky zkracuje zápis.
$o = new MyClass($parameter);
foreach ($o->id($id) AS $record) {
}
// narozdíl od
foreach (MyClass::construct($parameter)->id($id) AS $record) {
}
// anebo
$cache = new Cache(MyStorage::construct()->setParam('x', 'y'));
Editoval Milo (3. 11. 2011 9:23)

- Honza Marek
- Člen | 1664
Stačí počkat pár let na PHP 5.5 a nebude to potřeba ;)
Nebo pokud ti jde o stručnost, tak si můžeš udělat takovou tu zvrácenou funkci
function vratSe($object)
{
return $object;
}
vratSe(new Vec())->fluent();

- Honza Marek
- Člen | 1664
Těžko říct. Mám takovej neurčitej pocit, že Rasmus na WebExpu povidal, že tohle se do 5.4 nedostane.

- David Grudl
- Nette Core | 8285
Honza Marek napsal(a):
Stačí počkat pár let na PHP 5.5 a nebude to potřeba ;)
Nette\Object je vlastně jen fixem na to čekání ;-) Proto mi metoda newInstance příp newInstanceArgs připadá fajn, protože řeší něco, co se musí v PHP zatím obcházet.

- Milo
- Nette Core | 1283
HosipLan napsal(a):
@Milo: Znáš __callStatic() ?
Znám. Ale teď mi asi uplně nedochází, jak ji využít. Současná
implementace Nette\Object::__callStatic() vyvolá
MemberAccessException.
Stále mi přijde nejsnazší řešení vytvořit třídu
ObjectFactory která bude tuto funkčnost implementovat. Současná
kompatibilita se nezmění, kdo bude chtít funkčnost využít, nepodědí
Object ale ObjectFactory. Jako nevýhodu vidím
nefunkčnost v PHP <= 5.2 a špatný soubor:řádek ve fatal erroru při
chybném počtu parametrů předaných konstruktoru.
@Honza Marek: Zvrácenou funkci jsem jeden čas používal, ale pak jsem přestal protože se mi objevil hloupý konflikt s cizí knihovnou. Ale zase fungovala ve všech verzích PHP.
Editoval Milo (3. 11. 2011 16:59)

- Milo
- Nette Core | 1283
HosipLan napsal(a):
@Milo: Znáš __callStatic() ?
Asi mi docvalko. Máš na mysli… ?
abstract class Nette\Object
{
public static function __callStatic($name, $args)
{
if($name === 'construct') {
$class = get_called_class();
$ref = new \ReflectionClass($class);
if (!$ref->hasMethod('__construct')) {
return new $class;
}
return $ref->newInstanceArgs($args);
}
return ObjectMixin::callStatic(get_called_class(), $name, $args);
}
}

- Filip Procházka
- Moderator | 4668
Přesně tak :) Jenom se mi moc nelíbí construct, jsem spíš pro
newInstance().

- Milo
- Nette Core | 1283
Tak co třeba takhle? Má cenu psál pull a testy?
public static function __callStatic($name, $args)
{
$methodName = strtolower($name);
if ($methodName === 'newinstance' || $methodName === 'newinstanceargs') {
$class = get_called_class();
$ref = new \ReflectionClass($class);
if (!$ref->hasMethod('__construct')) {
return new $class;
}
if ($methodName === 'newinstanceargs') {
if (!array_key_exists(0, $args)) {
$args = array();
} elseif ($args[0] instanceof \Traversable) {
$args = iterator_to_array($args[0], false);
} elseif (!is_array($args[0])) {
throw new InvalidArgumentException("First parameter of $class::newInstanceArgs() must be an array, " . gettype($args[0]) . " given.");
} else {
$args = & $args[0];
}
}
return $ref->newInstanceArgs($args);
}
return ObjectMixin::callStatic(get_called_class(), $name, $args);
}

- internally
- Člen | 1
FYI PHP 5.4 již patch pro „instance method call“ obsahuje, http://svn.php.net/…PHP_5_4/NEWS?…, takže bych si dal pohov a věnoval se raději nějakým těm bugům a pull requestům, na githubu jich vidím požehnaně

- Majkl578
- Moderator | 1364
Tenhle návrh (jak už tu padlo) přichází s křížkem po funuse. Totiž v době PHP 5.4 chtít něco výhradně pro PHP 5.2 je k zamyšlení (5.2 je už skoro něco jako PHP 4) – v 5.3 jde snadno workaroundovat a v 5.4 je to podporované nativně:
(new Something)->foo();
(new Something(1, 'arg2'))->foo();
Milo napsal(a):
Člověče, Tebe to nějak rozčílilo…
Ale má pravdu. :)

- Majkl578
- Moderator | 1364
Milo napsal(a):
Jaký snadný workaround pro 5.3 máš na mysli? Na nic univerzálnějšího jsem nepřišel.
Vpodstatě to s get_called_class (nepřijde mi to tak problematické), příp. třeba:
use Nette\Reflection\ClassType;
ClassType::from(get_called_class())->newInstance(1, 'arg');
>
Za jak dlouho myslíte, že 5.4 bude stable?
Do konce roku, maximálně v lednu. Už je RC1.

- David Grudl
- Nette Core | 8285
internally napsal(a):
FYI PHP 5.4 již patch pro „instance method call“ obsahuje, http://svn.php.net/…PHP_5_4/NEWS?…, takže bych si dal pohov a věnoval se raději nějakým těm bugům a pull requestům, na githubu jich vidím požehnaně
To je dobrá zpráva, že se chceš věnovat bugům, ale za celý měsíc mi od tebe žádný fix nepřišel. Na čem to vázne?