@method: methods that we hate to write
- David Grudl
- Nette Core | 8228
I implemented to Nette\Object support for dynamically created methods declared using annotations. It is syntactic sugar for methods that we do not like to write. Useful and intended for data transfer objects or so-called entities.
Let's have simple entity:
class Entity extends Nette\Object
{
/** @var string */
public $name;
/** @var bool */
public $enabled;
}
Nice, but there is no encapsulation. So let's change public to private:
class Entity extends Nette\Object
{
/** @var string */
private $name;
/** @var bool */
private $enabled;
public function setName(string $name) // hmmm, this is maybe PHP 6.0 typehint
{
$this->name = (string) $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function setEnabled(bool $on)
{
$this->enabled = (bool) $on;
return $this;
}
public function isEnabled()
{
return $this->enabled;
}
}
There are 4 boring methods! And typehints used by setters, they are still PHP wet dream (and I think it is absolutely incomprehensible lack of PHP).
Let's Nette\Object generate these methods:
/**
* @method setName(string)
* @method string getName()
* @method setEnabled(bool)
* @method bool isEnabled()
*/
class Entity extends Nette\Object
{
/** @var string */
private $name;
/** @var bool */
private $enabled;
}
$obj = new Entity;
$obj->setName('hello'); // it works!
There is special support for arrays:
/**
* @method addItem(string)
* @method setItems(string[])
* @method string[] getItems
*/
class Entity extends Nette\Object
{
protected $items;
}
$obj = new Entity;
$obj->addItem('hello');
It is useful to declare typehints directly in @method
annotations, but if you omit them, @var
annotations are good
enough:
/**
* @method addItem
* @method setItems
* @method getItems
*/
class Entity extends Nette\Object
{
/** @var string[] */
protected $items;
}
Of course, all these typehints are taken into consideration:
// there is typehint string[]
$obj->setItems(array(1, 2));
dump($obj->getItems()); // prints array of *strings* array('1', '2');
// or
$obj->setItems(1); // throws: Argument passed to Entity::setItems() must be string[], integer given
Magic getters and setters act like normal methods. So you can use Nette\Object features with them. Eg. you can store method to variable:
$obj = new Entity;
$method = $obj->addItem;
$method('abc');
and first of all, you can count with well known magic properties!
$items = $obj->items; // calls Entity::getItems()
Magic properties for magic methods, it's magic2.
Enjoy it!
- pawouk
- Member | 172
Nice one!
public function setName(string $name) // yes, this is maybe PHP 6.0 typehint
{
$this->name = (string) $name;
return $this;
}
So what about to create simple object like Nette\String, Nette\Int … than you need not to wait to PHP 6. But it maybe turn aplication slow. And basic function like is_scalar, strpos atd will not work :-(
- Jan Tvrdík
- Nette guru | 2595
@David Grudl: How does this implementation affects
performance of all current classes using Nette\Object
?
- frosty22
- Member | 373
Well, i like because setters/getters method are about 70% content of all my classes :( I generate it via IDE, but still there are. I use something like this for Doctrine entities already (https://github.com/…seEntity.php).
Last edited by frosty22 (2013-05-06 09:14)
- vrana
- Member | 131
I suppose that $obj->name = 'a'
works and checks the type
thanks to another magic property of Nette\Object
(getters and
setters allow access through variable), right? It could be a little confusing
that you can access a private property but it seems like the right behavior.
The only thing that probably wouldn't work is
$obj->items[] = 1
. Or were you able to circumvent this problem
(“Indirect modification of overloaded property”) somehow too?
- Majkl578
- Moderator | 1364
Few more things:
- Why don't we support indexed arrays (like e.g. 2nd argument being a key)?
- Why don't we support removers? Having adders implicates having also removers.
- Why throwing an exception if more parameters is given? It's not an error in PHP itself.
Additionally, I don't think this should be part of Object itself, rather a separate trait, but that's a problem until traits are supported.
vrana wrote:
It could be a little confusing that you can access a private property but it seems like the right behavior.
Agreed. It might seem really strange, using two magics at once.
- Tharos
- Member | 1030
add<something>
doesn't
respect English grammar.
elf = elves (not elfs)
calf = calves (not calfs)
knife = knives (dtto)
loaf = loaves
shelf = shelves
wolf = wolves
loaf = loaves
man = men
person = people
mouse = mice
child = children
foot = feet
goose = geese
tooth = teeth
louse = lice
cactus = cacti
appendix = appendices
ox = oxen
Last edited by Tharos (2013-06-12 13:04)
- duke
- Member | 650
I've noticed that if I use @method property to define a setter, autowiring in config won't work for that setter (I have to explicitly specify the argument). Is that a bug or intended behavior?
E.g. something like:
namespace MyApp\AdminModule;
use MyApp;
/**
* Authentication presenter.
* @method setUserManager(MyApp\UserManager)
*/
class AuthPresenter extends BasePresenter
{
/** @var MyApp\UserManager */
private $userManager;
...
}
services:
UserManager:
class: MyApp\UserManager
AdminAuthPresenter:
class: MyApp\AdminModule\AuthPresenter
setup:
- setUserManager # this will result in Nette\InvalidArgumentException: MyApp\AdminModule\AuthPresenter::setUserManager() expects 1 argument, 0 given.
# - setUserManager(@UserManager) # this would work
- Tomáš Votruba
- Moderator | 1114
@duke This is intended behavior. Setters are not autowired.
And this is typehint control:
/**
* @method setUserManager(MyApp\UserManager)
*/
- Tomáš Votruba
- Moderator | 1114
@duke Didn't know that. Where is that behavior in the code?
So this would work with hand-written setter?
services:
UserManager:
class: MyApp\UserManager
AdminAuthPresenter:
class: MyApp\AdminModule\AuthPresenter
setup:
- setUserManager
Last edited by Tomáš Votruba (2014-07-19 23:52)
- duke
- Member | 650
Autowiring of arguments for regular methods seems to be handled by this code. I don't know how exactly the magical setters are handled, but most likely the cause for autowiring not working for them is the lack of support in relevant reflection classes providing parameters info for these methods.
And answer to your 2nd question is yes.
Last edited by duke (2014-07-20 12:04)
- David Grudl
- Nette Core | 8228
Missing ReflectionMethod can be “mocked” this way https://api.nette.org/…der.php.html#550
- Tomáš Votruba
- Moderator | 1114
@duke Thanks for explaining, I'm bit smarter now :).
What do you recommend based on your practice?
- magic methods with neon configuration or
- hand written method with autowiring?
- Majkl578
- Moderator | 1364
David Grudl wrote:
Missing ReflectionMethod can be “mocked” this way https://api.nette.org/…der.php.html#550
Using eval + lambda function is faster and has significantly smaller memory footprint (see here create_function and here eval + lambda), also create_function is sometimes disabled on shared hostings for so-called security purposes.