PHP Magic __invoke upon object-property of a class - php

Consider this class arrangement - and in particular the magic function __invoke:
class Barman {
public function __construct() {
// .. .constructor stuff - whatever
}
public function makeDrink() {
return "vodka martini, shaken";
}
}
class Bar {
private $arr_barmen = array();
public function __construct() {
$this->arr_barmen['john'] = new Barman();
}
public function __invoke($barman_id) {
echo "I have been invoked";
return $this->arr_barmen[$barman_id];
}
public function aBarFunc($param) {
return "yes it worked ," .$param;
}
}
class Foo {
public $myBar;
public function __construct() {
$this->myBar = new Bar();
}
}
I want to write syntax like this
$company = new Foo();
$company->myBar('john')->makeDrink();
Preferred result:
"vodka martini, shaken"
Actual result:
"Call to undefined method Foo::myBar()"
Invoking myBar() with the magic method should return a barman Object upon which you can call any of the barman's public methods
But now consider this (which does work)
$company = new Foo();
$myBar = $company->myBar;
$drink = $myBar('john')->makeDrink();
echo $drink;
// Result:
// I have been invoked
// vodka martini, shaken
So what's going on? I don't like that workaround - it's not sleek.
I need it to work this way:
$company->myBar('john')->makeDrink();
Please help? :-)

I believe you can just add braces around it:
$company = new Foo();
$drink = ($company->myBar)('john')->makeDrink();
echo $drink; // vodka martini, shaken

This is being caused by an ambiguity in the call you're trying to make:
$company->myBar('john')->makeDrink();
Because myBar is a property, the PHP interpreter isn't expecting it to be callable. It is parsing it as an attempt to call a method called myBar() which doesn't exist, and is thus throwing the error.
The direct way to resolve this is to clarify the ambiguity for the interpreter. You do this by adding curly braces around the property, as follows:
$company->{myBar}('john')->makeDrink();
The code is now explicit that myBar is a property and should be accessed as such, but that it contains a value that is callable and that you wish to make that call.
This whole topic is complicated (slightly) by the fact that PHP 5.x and PHP 7.x behave differently with regard to how they default to handling these kinds of ambiguity. PHP 7 changed the defaults in order to correct some internal inconsistencies within the language. The result is that in situations like this where you have an ambiguity, if you want your code to work across both PHP 5.x and 7.x, you should always use the braces to explicitly define how you want it to work, regardless of whether your code works for you without them.
There is some documentation about this change in the PHP 7.0 upgrade notes, although the examples given don't cover your exact situation.

To attain chaining you have to return the Barman object in invoke.
class Barman {
public function __construct() {
// .. .constructor stuff - whatever
}
public function makeDrink() {
return "vodka martini, shaken";
}
}
class Bar {
private $arr_barmen = array();
public function __construct() {
$this->arr_barmen['john'] = new Barman();
}
public function __invoke($barman_id) {
echo "I have been invoked";
return $this->arr_barmen[$barman_id] = new Barman();
}
public function aBarFunc($param) {
return "yes it worked ," . $param;
}
}
class Foo {
public $myBar;
public function __construct() {
$this->myBar = new Bar();
}
// create a function with variable name to invoke the object
public function myBar($name) {
$mybar = $this->myBar;
return $mybar($name);
}
}

Thank you for the responses. I devised a cute workaround as follows:
class Barman {
public function __construct() {
}
public function makeDrink() {
return "vodka martini, shaken";
}
}
class Bar {
private $arr_barmen = array();
public function __construct() {
$this->arr_barmen['john'] = new Barman();
}
public function getBarman($barman_id) {
return $this->arr_barmen[$barman_id];
}
public function __invoke($barman_id) {
echo "I have been invoked \n";
return $this->arr_barmen[$barman_id];
}
}
class Foo {
private $_myBar;
public function __construct() {
$this->_myBar = new Bar();
}
// The fix
public function myBar($barman_id) {
return $this->_myBar->getBarman($barman_id);
}
}
Usage:
$company = new Foo();
$drink = $company->myBar('john')->makeDrink();
echo $drink; // vodka martini, shaken
How it works?
Foo->myBar becomes private (Foo->$_myBar);
we create a public function with the name myBar inside Foo;
we create a "getBarman" fuction inside Bar which is called from Foo->myBar('john')
A few more steps - and now there's no ambiguity - Foo->myBar() IS always a function.
Cheers
M

Related

Class variables holding a function in PHP

PHP allows for variables to hold functions like so:
$f = function($a,$b) {
print "$a $b";
};
$f("Hello","World!"); //prints 'Hello World!'
This works just fine for me. I'm trying to pass a function into a class and set an instance variable to hold that function but with little luck:
class Clusterer {
private $distanceFunc;
public function __construct($f) {
$this->distanceFunc = $f;
print $f(1,7); //works
print $this->distanceFunc(1,7); //exceptions and errors abound
}
}
$func = function($a,$b) {
return abs($a-$b);
}
$c = new Clusterer($func);
Am I doing something wrong here? The error is that the function doesn't exist so my guess currently is that it looks for a class function with that name (which there isn't one) and then gives up rather than looking for variables as well... how can I make it view the $this->distanceFunc as a variable?
EDIT:
So after the advice from the answers below, I found a solution which was the make a function to wrap the invocation. For example my class is now:
class Clusterer {
private $distanceFunc;
public function __construct($f) {
$this->distanceFunc = $f;
print $f(1,7); //works
print $this->distanceFunc(1,7); //exceptions and errors abound
}
private function distanceFunc($a,$b) {
$holder = $this->distanceFunc;
return $holder($a,$b);
}
}
$func = function($a,$b) {
return abs($a-$b);
}
$c = new Clusterer($func);
and this works great. Php looks for functions first and can only tell if it is a variable by context I guess is the moral of this story.
Your code doesn't work because PHP interprets $this->distanceFunc(1,7) as a class method, but you can do the following:
class Clusterer {
private $distanceFunc;
public function __construct($f) {
$this->distanceFunc = $f;
print $f(1,7); //works
print call_user_func_array($this->distanceFunc, array(1, 7));
// print $this->distanceFunc(1,7); //exceptions and errors abound
}
}
$func = function($a,$b) {
return abs($a-$b);
};
$c = new Clusterer($func);
http://sandbox.onlinephpfunctions.com/code/cdc1bd6bd50f62d5c88631387ac9543368069310
In PHP, methods and properties of an object occupy separate namespaces. This is different from JavaScript, for example, where foo.bar = function() {} is a perfectly valid way of defining a method.
Consequently, $this->distanceFunc(1,7); looks for a method named distanceFunc on the current class, and the classes it inherits from, but never looks for the property which you happen to have given the same name.
One solution is to force PHP to look up a property, then execute it, e.g. $foo = $this->distanceFunc; $foo(1,7) or call_user_func($this->distanceFunc, 1, 7)
Another would be to define the magic method __call on your class, which gets run whenever a non-existent method is referenced. Something like this ought to work (I don't have an easy way to testright now):
function __call($func, $args) {
if ( property_exists($this, $func) && is_callable($this->$func) ) {
return call_user_func_array($this->$func, $args);
}
}
Note that this still isn't the same as a real method, for instance in terms of access to private properties.
It looks like you're going for a strategy pattern here. IE you want to be able to inject different methods for calculating distance? If so there is a more "sane" way to do it.
You can define an interface to the classes you will use to store the strategy method ensuring that the class will always have the method calculate() for example which would be your distance calculation function. Then in the constructor of your Clusterer class, type check against the interface in the parameter and call calculate() on the object passed in.
Looks like this:
interface Calculateable
{
public function calculate();
}
class MyDistanceCalculator implements Calculateable
{
public function calculate()
{
// Your function here
}
}
class Clusterer
{
protected $calc;
public function __construct(Calculateable $calc)
{
$this->calc = $calc;
$this->calc->calculate();
}
}
$myClusterer = new Clusterer(new MyDistanceCalculator());
Because you defined an interface, any object you pass in will have the calculate() function
In HHVM, you can do this:
<?php
class Foo
{
public function __construct()
{
$this->bar = function() { echo "Here\n"; };
($this->bar)();
}
}
new Foo();
But it's not yet supported in PHP. But, it will be in PHP 7 (there will be no release named PHP 6).
PHP doesn't have first class functions. In JavaScript if you returned a function you could do this: myFunctionThatReturnsAFunction()(1,2), but not in PHP.
<?php
class Clusterer {
private $distanceFunc;
public function __construct(Closure $f) {
$this->distanceFunc = $f;
}
public function getDistFunc()
{
return $this->distanceFunc;
}
}
$func = function($a,$b) {
return abs($a-$b);
};
$c = new Clusterer($func);
$a = $c->getDistFunc();
echo $a(1,2);
Take a look at call_user_func
class Clusterer {
private $distanceFunc;
public function __construct($f) {
$this->distanceFunc = $f;
print $f(1,7); //works
print call_user_func($this->distanceFunc, 1, 7); //works too ;)
}
}
$func = function($a,$b) {
return abs($a-$b);
};
$c = new Clusterer($func);
Don't ask me what is the difference, but it works the way you want (One of the reasons i hate this language)

php OOP find out if method argument is a method in child class

The code sample is an simple example for what i'm working on.
I have 2 classes in php.
class Wrap {
public function wrapA($arg){
return 'A'.$arg.'A';
}
public function wrapB($arg){
return 'B'.$arg.'B';
}
}
class Child extends Wrap {
public $OUT;
public function wrapA($arg){
$this->OUT .= parent::wrapA($arg);
}
public function wrapB($arg){
$this->OUT .= parent::wrapB($arg);
}
public function __destruct(){
echo $this->OUT;
}
}
$X = new Child();
$X->wrapA(
$X->wrapB('CC')
);
The Output here is "BCCBAA". But what I try to achieve is "ABCCBA".
The "Wrap" class must be in this form.
… and if I have the following method-calls:
$X->wrapB( $X->wrapA('1') );
$X->wrapA( $X->wrapB('aa') .$X->wrapA('bbb') .$X->wrapB(
$X->wrapA('cccc') ) );
… i want to have the following output: BA1ABABaaBAbbbABAcccABA
Is there an other way?
I also want the Wrap-Class to work alone (without Child) … this is why the methods have return-value.
But in Child-Class I want to write the return-values in a variable.
THX in advance!
That's because $X->wrapB('CC') doesn't return anything and gets cast to an empty string by the time $X->wrapA() is called, thus A gets wrapped around nothing.
However, because you append BCCB to $X->OUT, by the time you call $X->wrapA(), it appends AA to that, leading to BCCBAA.
After looking at the question again, I feel that it should be solved in another way; this is something to consider:
class Wrap
{
// The wrapping itself can be declared as a static method
protected static function wrapWithChar($arg, $ch)
{
return $ch . $arg . $ch;
}
}
class Child extends Wrap
{
protected $OUT;
// we allow the internal state to be set upon construction
public function __construct($s = '')
{
$this->OUT = $s;
}
// no arguments required here, this gets applied on the internal state
public function wrapA()
{
$this->OUT = self::wrapWithChar($this->OUT, 'A');
// return instance to allow chaining
return $this;
}
public function wrapB()
{
$this->OUT = self::wrapWithChar($this->OUT, 'B');
return $this;
}
public function __toString()
{
return $this->OUT;
}
public function __destruct(){
echo $this->OUT;
}
}
// initialize with 'CC'
$X = new Child('CC');
// wrap B around it; becomes 'BCCB'
$X->wrapB();
// wrap A around that; becomes 'ABCCBA'
$X->wrapA();
// example of chaining
$Y = new Child('ZZ');
// wrap B then A around it; becomes 'ABZZBA'
$Y->wrapB()->wrapA();
Old answer
To make Child appear as something that Wrap can perform on, you could make use of the __toString() magic method (using instanceof would be more explicit, but also a bit more work):
class Child extends Wrap
{
public $OUT;
public function wrapA($arg)
{
$this->OUT = parent::wrapA($arg);
return $this;
}
public function wrapB($arg)
{
$this->OUT = parent::wrapB($arg);
return $this;
}
public function __toString()
{
return $this->OUT;
}
public function __destruct(){
echo $this->OUT;
}
}
Each wrapX() method now returns the instance itself, and __toString() gets called whenever it needs to be wrapped.
The above will generate the correct result.
I added this to my favorites as an interesting puzzle to solve.
And then found that it wasn't that complicated after I woke up and looked at the problem again.
I honestly don't think you should be using subclassing at this point since technically Child is not logically the a child of the Wrap class, it essentially seems to be a guy that wants to store the output of wrap's results.
so.. Here's my modifications that works with your original interface. Hope it's good for you ;).
It makes some very magical use of magic methods.
<?php
class Wrap {
public function wrapA($arg){
return 'A'.$arg.'A';
}
public function wrapB($arg){
return 'B'.$arg.'B';
}
}
class WrapReader{
protected $wrapper;
protected $currentResult;
public function __construct(Wrap $wrapper)
{
$this->wrapper = $wrapper;
}
public function __call($method,$argument)
{
$argument = $argument[0];
if(!method_exists($this->wrapper,$method))
throw new MethodNotFoundException('Method: '.$method.'() does not exist in class: '.get_class($this->wrapper));
$this->currentResult = $this->wrapper->$method($argument);
return $this->currentResult;
}
public function __destruct(){
echo $this;
}
public function __toString()
{
return $this->currentResult;
}
}
class MethodNotFoundException extends Exception{}
The usage:
$reader = new WrapReader(new Wrap());
echo $reader->wrapB( $reader->wrapA('1') );
echo $reader->wrapA( $reader->wrapB('aa') .$reader->wrapA('bbb') .$reader->wrapB( $reader->wrapA('cccc') ) );
echo '<br>';
Outputs BA1ABABaaBAbbbABAccccABA
Which is what you posted in your original question.

Is it possible to use mixins in php

I came to know about mixins.So my doubt is, is it possible to use mixins in php?If yes then how?
Use Trait introduced in PHP 5.4
<?php
class Base {
public function sayHello() {
echo 'Hello ';
}
}
trait SayWorld {
public function sayHello() {
parent::sayHello();
echo 'World!';
}
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
?>
which prints Hello World!
http://php.net/manual/en/language.oop5.traits.php
This answer is obsolete as of PHP 5.4. See Jeanno's answer for how to use traits.
It really depends on what level of mixins you want from PHP. PHP handles single-inheritance, and abstract classes, which can get you most of the way.
Of course the best part of mixins is that they're interchangeable snippets added to whatever class needs them.
To get around the multiple inheritance issue, you could use include to pull in snippets of code. You'll likely have to dump in some boilerplate code to get it to work properly in some cases, but it would certainly help towards keeping your programs DRY.
Example:
class Foo
{
public function bar( $baz )
{
include('mixins/bar');
return $result;
}
}
class Fizz
{
public function bar( $baz )
{
include('mixins/bar');
return $result;
}
}
It's not as direct as being able to define a class as class Foo mixin Bar, but it should get you most of the way there. There are some drawbacks: you need to keep the same parameter names and return variable names, you'll need to pass other data that relies on context such as func_get_args_array or __FILE__.
Mixins for PHP (PHP does not implement Mixins natively, but this library will help)
First google result for "php5 mixin": http://www.sitepoint.com/forums/php-application-design-147/ruby-like-mixins-php5-332491.html
First google result for "php mixin": http://www.advogato.org/article/470.html
Short answer: yes, but not natively (yet, evidently, as #mchl notes). Check those out.
Longer answer: if you're using runkit, checkout runkit_method_copy(): "Copies a method from class to another."
I based mixins functionality on the blog entry found at jansch.nl.
class Node
{
protected $__decorator_lookup = array();
public function __construct($classes = array())
{
foreach($classes as $class)
if (class_exists($class))
{
$decorator = new $class($this);
$methods = get_class_methods($decorator);
if (is_array($methods))
foreach($methods as $method)
$this->__decorator_lookup[strtolower($method)] = $decorator;
}
else
trigger_error("Tried to inherit non-existant class", E_USER_ERROR);
}
public function __get($name)
{
switch($name)
{
default:
if ($this->__decorator_lookup[strtolower($name)])
return $this->__call($name);
}
}
public function __call($method, $args = array())
{
if(isset($this->__decorator_lookup[strtolower($method)]))
return call_user_func_array(array($this->__decorator_lookup[strtolower($method)], $method), $args);
else
trigger_error("Call to undefined method " . get_class($this) . "::$method()", E_USER_ERROR);
}
public function __clone()
{
$temp = $this->decorators;
$this->decorators = array();
foreach($temp as $decorator)
{
$new = clone($decorator);
$new->__self = $this;
$this->decorators[] = $new;
}
}
}
class Decorator
{
public $__self;
public function __construct($__self)
{
$this->__self = $__self;
}
public function &__get($key)
{
return $this->__self->$key;
}
public function __call($method, $arguments)
{
return call_user_func_array(array($this->__self, $method), $arguments);
}
public function __set($key, $value)
{
$this->__self->$key = $value;
}
}
class Pretty extends Decorator
{
public function A()
{
echo "a";
}
public function B()
{
$this->b = "b";
}
}
$a = new Node(array("Pretty"));
$a->A(); // outputs "a"
$a->B();
echo($a->b); // outputs "b"
EDIT:
As PHP clone is shallow, added __clone support.
Also, bear in mind that unset WON'T work (or at least I've not managed to make it work) within the mixin. So - doing something like unset($this->__self->someValue); won't unset the value on Node. Don't know why, as in theory it should work. Funny enough unset($this->__self->someValue); var_dump(isset($this->__self->someValue)); will produce correctly false, however accessing the value from Node scope (as Node->someValue) will still produce true. There's some strange voodoo there.

Magic Method __set() on a Instantiated Object

Ok i have a problem, sorry if i cant explaint it clear but the code speaks for its self.
i have a class which generates objects from a given class name;
Say we say the class is Modules:
public function name($name)
{
$this->includeModule($name);
try
{
$module = new ReflectionClass($name);
$instance = $module->isInstantiable() ? $module->newInstance() : "Err";
$this->addDelegate($instance);
}
catch(Exception $e)
{
Modules::Name("Logger")->log($e->getMessage());
}
return $this;
}
The AddDelegate Method:
protected function addDelegate($delegate)
{
$this->aDelegates[] = $delegate;
}
The __call Method
public function __call($methodName, $parameters)
{
$delegated = false;
foreach ($this->aDelegates as $delegate)
{
if(class_exists(get_class($delegate)))
{
if(method_exists($delegate,$methodName))
{
$method = new ReflectionMethod(get_class($delegate), $methodName);
$function = array($delegate, $methodName);
return call_user_func_array($function, $parameters);
}
}
}
The __get Method
public function __get($property)
{
foreach($this->aDelegates as $delegate)
{
if ($delegate->$property !== false)
{
return $delegate->$property;
}
}
}
All this works fine expect the function __set
public function __set($property,$value)
{
//print_r($this->aDelegates);
foreach($this->aDelegates as $k=>$delegate)
{
//print_r($k);
//print_r($delegate);
if (property_exists($delegate, $property))
{
$delegate->$property = $value;
}
}
//$this->addDelegate($delegate);
print_r($this->aDelegates);
}
class tester
{
public function __set($name,$value)
{
self::$module->name(self::$name)->__set($name,$value);
}
}
Module::test("logger")->log("test"); // this logs, it works
echo Module::test("logger")->path; //prints /home/bla/test/ this is also correct
But i cant set any value to class log like this
Module::tester("logger")->path ="/home/bla/test/log/";
The path property of class logger is public so its not a problem of protected or private property access.
How can i solve this issue? I hope i could explain my problem clear.
EDIT:
A simple demonstration
Modules::Name("XML_Helper")->xmlVersion ="Hello"; // default is 333
$a = Modules::Name("XML_Helper")->xmlVersion; // now $a should contain "Hello"
echo $a; // prints 333
What i need is
Modules::Name("XML_Helper")->xmlVersion ="Hello"; // default is 333
$a = Modules::Name("XML_Helper")->xmlVersion; // now $a should contain "Hello"
echo $a; // prints Hello
I realise you already said that path is public, but it's still worth mentioning: If you're using PHP 5.3.0+, note this quirk of property_exists():
5.3.0 | This function checks the existence of a property independent of
accessibility
In other words, if you check if (property_exists($delegate, $property)), you have no guarantee you have access to $delegate->$property for writing (or reading, for that matter, but you are trying to write).
As for actual troubleshooting: You could try checking if your if (property_exists($delegate, $property)) statement actually executes. If it doesn't, check the case of $property.
Sidenote: It's fairly hard to read the code you posted up, which makes it a bit of a pain to troubleshoot. Could you edit your post and indent it properly?
The path property of class logger is public so its not a problem of
protected or private property access.
That's your problem. From the docs:
__set() is run when writing data to inaccessible properties.
That suggests that __set() is not called for public properties.

Dynamically picking variable from a class

I'm trying to get data from a class in php5, where the data in the class is private and the calling function is requesting a piece of data from the class. I want to be able to gain that specific piece of data from the private variables without using a case statement.
I want to do something to the effect of:
public function get_data($field)
{
return $this->(variable with name passed in $field, i.e. name);
}
You could just use
class Muffin
{
private $_colour = 'red';
public function get_data($field)
{
return $this->$field;
}
}
Then you could do:
$a = new Muffin();
var_dump($a->get_data('_colour'));
<?php
public function get_data($field)
{
return $this->{$field};
}
?>
You may want to look at the magical __get() function too, e.g.:
<?php
class Foo
{
private $prop = 'bar';
public function __get($key)
{
return $this->{$key};
}
}
$foo = new Foo();
echo $foo->prop;
?>
I would be careful with this kind of code, as it may allow too much of the class's internal data to be exposed.

Categories