I am using the following class to mimic anonymous objects in PHP:
class AnonymousObject
{
protected $methods = array();
public function __construct(array $options) {
$this->methods = $options;
}
public function __call($name, $arguments) {
$callable = null;
if (array_key_exists($name, $this->methods))
$callable = $this->methods[$name];
elseif(isset($this->$name))
$callable = $this->$name;
if (!is_callable($callable))
throw new BadMethodCallException("Method {$name} does not exist");
return call_user_func_array($callable, $arguments);
}
}
(https://gist.github.com/Mihailoff/3700483)
Now, as long as the declared functions stand on their own everything works fine, but whenever I try to call one function from the other like this ...
$anonymous = new AnonymousObject(array(
"foo" => function() { $this->bar(); },
"bar" => function() { }
));
then of course I get Fatal error: Using $this when not in object context
Is there any way to work around this problem?
You can use the bind() or bindTo() method of the Closure instance that represents the anonymous function.
<?php
class AnonymousObject
{
protected $methods = array();
public function __construct(array $options) {
$this->methods = $options;
}
public function __call($name, $arguments) {
$callable = null;
if (array_key_exists($name, $this->methods))
$callable = $this->methods[$name];
elseif(isset($this->$name))
$callable = $this->$name;
if (!is_callable($callable))
throw new BadMethodCallException("Method {$name} does not exists");
$callable = $callable->bindTo($this);
return call_user_func_array($callable, $arguments);
}
}
$anonymous = new AnonymousObject(array(
"foo" => function() { echo 'foo'; $this->bar(); },
"bar" => function() { echo 'bar'; }
));
$anonymous->foo();
(example not quite right, since it will work only with anonymous functions; not with all the other callable() alternatives like e.g. the $this->name part)
prints foobar.
Related
Try to call method_exists on method registred with call_user_func.
<?php
class stdClass1
{
public static $methods = [];
public function __call($method, $arguments) {
return call_user_func_array(Closure::bind(self::$methods[$method], $this, get_called_class()), $arguments);
}
public function __set($name, $value) {
if (is_callable($value)) {
self::$methods[$name] = $value;
} else {
parent::__set($name, $value);
}
}
}
class stdClass2
{
function stdRunMethod()
{
$obj = new stdClass1();
$obj->test = function () {
echo 'a simple function'.PHP_EOL;
};
var_dump(method_exists($obj, "test"));
}
}
$obj = new stdClass2();
$obj->stdRunMethod();
method_exists return false. How to check this method with method_exists? Why method_exists return false?
Because test is not a method. It's a property which stores anonymous function.
If you want to check if value of property can be called as function you can use is_callable:
var_dump(is_callable([$obj, "test"]));
Need dynamically append method to class.
My code:
<?php
class stdClass1 {
public function __call($method, $arguments) {
return call_user_func_array(Closure::bind($this->$method, $this, get_called_class()), $arguments);
}
}
class stdClass2 {
function stdRunMethod() {
$obj = new stdClass1();
$obj->test = function() {
echo 'a simple function';
};
$obj->test();
$obj2 = new stdClass1();
$obj2->test();
}
}
$obj = new stdClass2();
$obj->stdRunMethod();
Question: why test method run only for first instance of stdClass1 class? How to append this method for all new instances?
try this instead (demo):
<?php
class stdClass1 extends \stdClass
{
private static $addedClosures = array();
public function __set($name, $value)
{
if ($value instanceof \Closure) {
self::$addedClosures[$name] = $value;
}
else {
parent::__set($name, $value);
}
}
public function __call($method, $arguments)
{
if (isset(self::$addedClosures[$method]))
return call_user_func_array(self::$addedClosures[$method], $arguments);
return call_user_func_array($method, $arguments);
}
}
class stdClass2 extends \stdClass
{
function stdRunMethod()
{
$obj = new stdClass1();
$obj->test = function () {
print_r('a simple function');
};
$obj->test();
$obj2 = new stdClass1();
$obj2->test();
}
}
The reason it only runs once is that each copy of stdClass1 maintains their own set of variables. In the following
$obj1 = new stdClass1();
$obj1->a = '1';
$obj2 = new stdClass1();
$obj2->a = '2';
echo $obj1->a;
You'll get the value 1 as the output. Because in most cases they maintain different references. Unless you use the static keyword. Static properties are shared between all instances of the class, and should be used carefully, but that's what you're thinking of. What you're thinking of can be done like this
<?php
class stdClass1 {
private static $methods = [];
public function __call($method, $arguments) {
return call_user_func_array(Closure::bind($this->methods[$method], $this, get_called_class()), $arguments);
}
public function __set($name, $value) {
if (is_callable($value)) {
$this->methods[$name] = $value;
} else {
parent::__set($name, $value);
}
}
}
Here we're using a static, defined property to hold all of the dynamic methods, and we're using the magic __set property to set the methods in to the array.
That being said, dynamically loading methods in to an object is bad. Don't do that
I have two Classes viz foo & Bar
class bar extends foo
{
public $element = null;
public function __construct()
{
}
}
and the Class foo goes as
class foo implements ArrayAccess
{
private $data = [];
private $elementId = null;
public function __call($functionName, $arguments)
{
if ($this->elementId !== null) {
echo "Function $functionName called with arguments " . print_r($arguments, true);
}
return true;
}
public function __construct($id = null)
{
$this->elementId = $id;
}
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
public function offsetExists($offset)
{
return isset($this->data[$offset]);
}
public function offsetUnset($offset)
{
if ($this->offsetExists($offset)) {
unset($this->data[$offset]);
}
}
public function offsetGet($offset)
{
if (!$this->offsetExists($offset)) {
$this->$offset = new foo($offset);
}
}
}
i want that when i run the below piece of code:
$a = new bar();
$a['saysomething']->sayHello('Hello Said!');
should return Function sayHello Called with arguments Hello Said! from foo's __call magic method.
Here, i want to say is saysomething should be passed in $this->elementId from foo's __construct function and sayHello should be taken as method and Hello Said should be taken as parameters for sayHello Function which would be rendered from __call magic method.
Also, need to chain methods like:
$a['saysomething']->sayHello('Hello Said!')->sayBye('Good Bye!');
If I'm not mistaken, you should change foo::offsetGet() to this:
public function offsetGet($offset)
{
if (!$this->offsetExists($offset)) {
return new self($this->elementId);
} else {
return $this->data[$offset];
}
}
It returns an instance of itself if there's no element at the given offset.
That said, foo::__construct() should be called from bar::__construct() as well and be passed a value other than null:
class bar extends foo
{
public $element = null;
public function __construct()
{
parent::__construct(42);
}
}
Update
To chain calls, you need to return the instance from __call():
public function __call($functionName, $arguments)
{
if ($this->elementId !== null) {
echo "Function $functionName called with arguments " . print_r($arguments, true);
}
return $this;
}
I was wondering how we can use the call_user_func_array() safely in the code.
Following coded function way is safe ?
function outertext() {
// …
if ($this->dom && $this->dom->callback!==null) {
call_user_func_array($this->dom->callback, array($this));
}
// …
}
What is best possible use of the call_user_func_array() of PHP. how we can use this function safely
Proof of concept: (how attacker can attack on this function)
<?php
class simple_html_dom_node {
private $dom;
public function __construct() {
$callback = array(new WP_Screen(), 'render_screen_meta');
$this->dom = (object) array('callback' => $callback);
}
}
class WP_Screen {
private $_help_tabs;
public $action;
function __construct() {
$count = array('count' => 'echo "schwag" > /tmp/1337h4x0rs');
$this->action = (object) $count;
$this->_help_tabs = array(array(
'callback' => 'wp_generate_tag_cloud',
'topic_count_scale_callback' => 'shell_exec'));
}
}
echo serialize(new simple_html_dom_node()).'𝌆';
?>
Check this modified example
<?php
class WP_Screen {
private $_help_tabs;
public $action;
function __construct() {
$count = array('count' => 'echo "schwag" > /tmp/1337h4x0rs');
$this->action = (object) $count;
$this->_help_tabs = array(array(
'callback' => 'wp_generate_tag_cloud',
'topic_count_scale_callback' => 'shell_exec'));
}
public function render_screen_meta()
{
echo __METHOD__;
}
}
class simple_html_dom_node
{
private $dom;
public function __construct()
{
$callback = array(new WP_Screen(), 'render_screen_meta');
$this->dom = (object) array('callback'=>$callback);
}
public function outer_text()
{
//verify the dom callback function here
if(is_callable($this->dom->callback))
{
//invoke the method here
call_user_func_array($this->dom->callback, array());
}
}
}
//create an object
$obj = new simple_html_dom_node();
//invoke the method
$obj->outer_text();
checkout the following example
<?php
function foobar($arg, $arg2) {
echo __FUNCTION__, " got $arg and $arg2\n";
}
class foo {
function bar($arg, $arg2) {
echo __METHOD__, " got $arg and $arg2\n";
}
}
// Call the foobar() function with 2 arguments
call_user_func_array("foobar", array("one", "two"));
// Call the $foo->bar() method with 2 arguments
$foo = new foo;
call_user_func_array(array($foo, "bar"), array("three", "four"));
?>
Output would be
foobar got one and two
foo::bar got three and four
validate first the method exist in the class or not using any of the function
method_exists or is_callable
Reference:
http://in2.php.net/manual/en/function.is-callable.php
http://in2.php.net/method_exists
Example:
<?php
class someClass {
function someMethod()
{
}
}
$anObject = new someClass();
$methodVariable = array($anObject, 'someMethod');
is_callable($methodVariable, true, $callable_name);
if($callable_name)
{
//use your function call here
call_user_func_array(callback_function, array(object));
}
i was wanting to wrap an object in another - favoring composition over inheritance. but i am not sure i am doing it right tho there are no errors.
i created a class Wrapped thats is wrapped by Wrapper. i made it such that when a method/property is called on $wrapper, if it exists in the class, Wrapper, it will be returned else, it will delegate to the $wrapped object. i wonder apart from the fact i didnt check if the method/property exists, what have i done wrong? can some1 explain __callStatic() too?
class Wrapped {
protected $wrappedProp1 = 'Wrapped: Property 1';
protected $wrappedProp2 = 'Wrapped: Property 2';
function method1($arg1, $arg2) {
echo "Called Wrapped::method1() with the following parameters: $arg1, $arg2";
}
static function sMethod2() {
echo 'Called a static method in wrapped';
}
function __get($name) {
return $this->$name;
}
function __set($name, $val) {
$this->$name = $val;
}
}
class Wrapper {
protected $wrapperProp1 = 'Wrapper: Property 1';
protected $wrapped;
function __construct($wrapped) {
$this->wrapped = $wrapped;
}
function wrapperMethod() {
echo 'In wrapper method';
}
function __get($name) {
if (property_exists($this, $name)) {
return $this->$name;
}
return $this->wrapped->$name;
}
function __set($name, $val) {
if (property_exists($this, $name)) {
$this->$name = $val;
}
$this->wrapped->$name = $val;
}
function __call($name, $args = array()) {
call_user_func_array(array($this->wrapped, $name), $args);
}
static function __callStatic($name, $args = array()) {
call_user_func_array(array('Wrapped', $name), $args);
}
}
$wrapper = new Wrapper(new Wrapped);
// testing normal methods
$wrapper->wrapperMethod();
echo $wrapper->wrapperProp1;
$wrapper->wrapperProp1 = 'New Wrapper Prop 1';
echo $wrapper->wrapperProp1;
// testing delegates
$wrapper->method1('hello', 'world'); //delegated to Wrapped::method1()
$wrapper->sMethod2(); // delegated to static Wrapped::sMethod2() ... what is callStatic for then
echo $wrapper->wrappedProp2;
Wrapper::sMethod2();
As it seems - it's all ok.
About __callStatic() - it allows you to workaround undefined static functions in class.
Example:
<?php
class Foo {
static function __callStatic($name, $args = array()) {
echo "Called static function $name with arguments ". print_r($args, true);
}
}
Foo::Bar('test');
// will output "Called static function Bar with arguments Array ( 0 => test );"