So, I have something like this:
class ClassA{
public function doCallback($callback){
call_user_func_array($callback, array());
}
public function doSomething(){
return 12345;
}
}
class ClassB{
public function runMe(){
$classA = new ClassA();
$classA->doCallback(function(){
$this->doSomething();
});
}
}
I am trying to figure out how, if possible I can use $this or something similar in a callback function that will refer to the class that the callback is running on (Not the class it is in) if that makes sense.
So in my above example I would like $this->doSomething(); where $this means ClassA and not ClassB. Currently $this is referring to ClassB. Is there something that I can use to say I want ClassA?
EDIT
Here is the actual method that I am using
public function save(){
$id = (int)$this->input->post("id");
$title = $this->input->post("title");
$uid = (int)$this->session->get("id");
$this->db->getTable("content")
->has(array("user_id" => $uid, "name" => $title), function(){
echo json_encode(array("error" => "Name already exists."));
}, function() use($id, $uid, $title){
//$this->db->getTable("content")
$this->update(array("name" => $title), array(
"user_id" => $uid,
"content_id" => $id), function($rows){
if($rows > 0){
$error = "";
}else{
$error = "Unknown error occurred";
}
echo json_encode(array("error" => $error));
});
});
}
$this->db->getTable("content") returns a Database Object, and has() is a method in the object. I was hoping that I could use a shorthand way to access $this->db->getTable("content") without having to call it again in the callback, or passing it through call_user_func_array as a parameter or without the use of use().
the method has():
https://github.com/ZingPHP/Zing/blob/master/Zing/src/Modules/Database/DBOTable.php#L311
EDIT
I think in my callback function I need to do something like this, but I don't think it is working:
public function myFunc(callback $callback){
$callback->bindTo($this, $this);
return call_user_func_array($callback, array());
}
You can go through the public interface of ClassA.
class ClassA
{
public function doCallback($callback)
{
call_user_func_array($callback, array());
}
public function doSomething()
{
echo "Doing something...\n";
}
}
class ClassB
{
private $_oSubject;
public function __construct(ClassA $oSubject)
{
$this->_oSubject = $oSubject;
}
public function runMe()
{
$this->_oSubject->doCallback(function() {
$this->_oSubject->doSomething();
});
}
}
$oA = new ClassA();
$oB = new ClassB($oA);
$oB->runMe();
I got it! I just need to add $newCallback = $callback->bindTo($this, $this); in the callback class, and then in ClassB I can use $this to refer to ClassA.
class ClassA{
public function doCallback($callback){
$callback = $callback->bindTo($this, $this);
call_user_func_array($callback, array());
}
public function doSomething(){
return 12345;
}
}
class ClassB{
public function runMe(){
$classA = new ClassA();
$classA->doCallback(function(){
$this->doSomething();
});
}
}
Related
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));
}
Is there any way to bind $this to a closure that is passed as a parameter?
I read and reread everything I could find in manual or over the internet, but no one mentions this behaviour, except this blog post:
http://www.christophh.net/2011/10/26/closure-object-binding-in-php-54/
which mentions it but doesn't show how to do it.
So here's an example. When calling the get(function() {}) method I want the callback function that is passed to it was bound to the object i.e. bound to $this, but unfortunately it doesn't work. Is there any way I can do it?
class APP
{
public $var = 25;
public function __construct() {
}
public function get($callback) {
if (!is_callable($callback)) {
throw new InvalidArgumentException('Paran must be callable.');
}
// $callback->bindTo($this);
$callback->bindTo($this, $this);
$callback();
}
}
$app = new APP();
$app->get(function() use ($app) {
echo '<pre>';
var_dump($app);
echo '<br />';
var_dump($this);
});
$app works. $this is NULL.
I actually didn't understand why using the bindTo method didn't work in this case, but I could get it to work using Closure::bind
public function get($callback) {
if (!is_callable($callback)) {
throw new InvalidArgumentException('Param must be callable.');
}
$bound = Closure::bind($callback, $this);
$bound();
}
Edit
Aparently the bindTo method has the same behavior, so you should reassign its return value to $callback. For example:
public function get($callback) {
if (!is_callable($callback)) {
throw new InvalidArgumentException('Param must be callable.');
}
$callback = $callback->bindTo($this);
$callback();
}
Do it like this:
class APP
{
public $var = 25;
public function __construct() {}
public function get($callback) {
if (!is_callable($callback)) {
throw new InvalidArgumentException('Param must be callable.');
}
// $callback->bindTo($this);
// you must save result in another var and call it
$callback1 = $callback->bindTo($this, $this);
$callback1();
}
}
$app = new APP();
$app->get(function() use ($app) {
echo '<pre>';
var_dump($app);
echo '<br />';
var_dump($this);
});
Just pass it as an argument:
public function get($callback) {
if (!is_callable($callback)) {
throw new InvalidArgumentException('Paran must be callable.');
}
// $callback->bindTo($this);
return $callback($this);
}
...
$app = new APP();
$app->get(function($that) use ($app) {
echo '<pre>';
var_dump($app);
echo '<br />';
var_dump($that);
});
Alternatively, if you really did need to bind it, you would have to use a function that returned a function, like this:
public function getCallback($callback) {
return function($app){
return $callback($this, $app);
}
}
...
$app = new APP();
$f = $app->get(function($that, $app) {
echo '<pre>';
var_dump($app);
echo '<br />';
var_dump($that);
});
$f($app);
A small correction: don't use is_callable to check if Closure is passed by parameter.
Because is_callable too accept String with name of function.
public function get(\Closure $callback) {
$bound = \Closure::bind($callback, $this);
$bound();
}
With is_callable we have this possibility:
$app = new App;
$app->get('my_function');
If function exists, this error is thrown:
Closure::bind() expects parameter 1 to be Closure, string given
Because "My_function" is passed in test of is_callable, but Closure::bind expects instance of Closure.
So this is my mixin class:
class AisisCore_Loader_Mixins {
private $_classes;
private $_class_objects = array();
private $_methods = array();
public function __construct(){
$this->init();
}
public function init(){}
public function setup($class){
if(!is_array($class)){
throw new AisisCore_Loader_LoaderException('Object passed in must be of type $class_name=>$params.');
}
$this->_classes = $class;
$this->get_class_objects();
$this->get_methods();
}
public function get_class_objects(){
foreach($this->_classes as $class_name=>$params){
$object = new ReflectionClass($class_name);
$object_name = get_class($object);
$this->_class_objects[$object->name] = $object->newInstanceArgs($params);
}
}
public function get_methods(){
foreach($this->_class_objects as $class_object_name => $class_object){
$this->_methods[$class_object_name] = get_class_methods($class_object);
}
return $this->_methods;
}
public function __call($name, $param = null){
foreach($this->_methods as $class_name=>$methods){
foreach($methods as $method){
if($name === $method){
return $this->isParam($class_name, $method, $param);
}
}
}
throw new AisisCore_Loader_LoaderException("Method: " .$name.
" does not exist or it's access is not public");
}
private function isParam($class_name, $method, $param){
if($param != null){
call_user_func(array($class_name, $method), $param);
}else{
call_user_func(array($class_name, $method));
}
}
}
Pretty simple stuff, load a set of classes, allow you to call their functions and so on, but we have a new issue. It seems that classes passed into this are instantiated as static, thus their methods cannot use $this-> they are resorted to using self:: which is wrong.
Lets see an example of how this all works:
class BaseBridge extends AisisCore_Loader_Mixins{
public function __construct(){
parent::construct();
$this->setup(array('ClassB' => array()));
}
}
Lets Define ClassB
class ClassB{
public function __construct(){}
public function some_method(){
$this->_some_private_method();
}
private function _some_private_method(){}
}
Pretty basic stuff, so lets hook it all up in ClassA
class ClassA extends BaseBridge{
public function __construct(){
parent::__construct();
$this->some_method();
}
}
Quick Review: We have a core class, ClassA which extends BaseBridge which is our bridge class between one or more (meant to be used with more) classes that ClassA extends from. In this case were only extending from ClassB for simplicity.
Whats the issue? See, how in ClassB, were doing: $this->_some_private_method(); ya that's going to epically and catastrophically fail. Why? because I get the error: Using $this when not in object context which makes me so confused, so I change it to: self::$_some_private_method(); and it works like a charm.
Why? and what do I have to change or fix to make it so that $this can be used in a class being instantiated through the mixin class?
So with some slight modifications, I have managed to make this work. How ever I do not believe that a function with multiple arguments will work - Feed back appreciated.
class AisisCore_Loader_Mixins {
private $_classes;
private $_class_objects = array();
private $_methods = array();
public function __construct(){
$this->init();
}
public function init(){}
public function setup($class){
if(!is_array($class)){
throw new AisisCore_Loader_LoaderException('Object passed in must be of type $class_name=>$params.');
}
$this->_classes = $class;
$this->get_class_objects();
$this->get_methods();
}
public function get_class_objects(){
foreach($this->_classes as $class_name=>$params){
$object = new ReflectionClass($class_name);
$this->_class_objects[$object->name] = $object->newInstanceArgs($params);
}
}
public function get_methods(){
foreach($this->_class_objects as $class_object_name => $class_object){
$this->_methods[$class_object_name] = get_class_methods($class_object);
}
return $this->_methods;
}
public function __call($name, $param = null){
foreach($this->_methods as $class_name=>$methods){
foreach($methods as $method){
if($name === $method){
return $this->_is_param($class_name, $method, $param);
}
}
}
throw new AisisCore_Loader_LoaderException("Method: " .$name.
" does not exist or it's access is not public");
}
private function _is_param($class_name, $method, $param){
if($param != null){
$this->_param_is_array($class_name, $method, $param);
}else{
call_user_func(array($this->_class_objects[$class_name], $method));
}
}
private function _param_is_array($class_name, $method, $param){
if(is_array($param)){
call_user_func_array(array($this->_class_objects[$class_name], $method), $param);
}else{
call_user_func(array($this->_class_objects[$class_name], $method, $param));
}
}
}
Now functions inside of classes that are registered by this class can use $this->.
The issue is that I am not sure if multiple param based functions will actually work.
I'm trying to add methods dynamically from external files.
Right now I have __call method in my class so when i call the method I want, __call includes it for me; the problem is I want to call loaded function by using my class, and I don't want loaded function outside of the class;
Class myClass
{
function__call($name, $args)
{
require_once($name.".php");
}
}
echoA.php:
function echoA()
{
echo("A");
}
then i want to use it like:
$myClass = new myClass();
$myClass->echoA();
Any advice will be appreciated.
Is this what you need?
$methodOne = function ()
{
echo "I am doing one.".PHP_EOL;
};
$methodTwo = function ()
{
echo "I am doing two.".PHP_EOL;
};
class Composite
{
function addMethod($name, $method)
{
$this->{$name} = $method;
}
public function __call($name, $arguments)
{
return call_user_func($this->{$name}, $arguments);
}
}
$one = new Composite();
$one -> addMethod("method1", $methodOne);
$one -> method1();
$one -> addMethod("method2", $methodTwo);
$one -> method2();
You cannot dynamically add methods to a class at runtime, period.*
PHP simply isn't a very duck-punchable language.
* Without ugly hacks.
You can dynamically add attributes and methods providing it is done through the constructor in the same way you can pass a function as argument of another function.
class Example {
function __construct($f)
{
$this->action=$f;
}
}
function fun() {
echo "hello\n";
}
$ex1 = new class('fun');
You can not call directlry $ex1->action(), it must be assigned to a variable and then you can call this variable like a function.
if i read the manual right,
the __call get called insted of the function, if the function dosn't exist
so you probely need to call it after you created it
Class myClass
{
function __call($name, $args)
{
require_once($name.".php");
$this->$name($args);
}
}
You can create an attribute in your class : methods=[]
and use create_function for create lambda function.
Stock it in the methods attribute, at index of the name of method you want.
use :
function __call($method, $arguments)
{
if(method_exists($this, $method))
$this->$method($arguments);
else
$this->methods[$method]($arguments);
}
to find and call good method.
What you are referring to is called Overloading. Read all about it in the PHP Manual
/**
* #method Talk hello(string $name)
* #method Talk goodbye(string $name)
*/
class Talk {
private $methods = [];
public function __construct(array $methods) {
$this->methods = $methods;
}
public function __call(string $method, array $arguments): Talk {
if ($func = $this->methods[$method] ?? false) {
$func(...$arguments);
return $this;
}
throw new \RuntimeException(sprintf('Missing %s method.'));
}
}
$howdy = new Talk([
'hello' => function(string $name) {
echo sprintf('Hello %s!%s', $name, PHP_EOL);
},
'goodbye' => function(string $name) {
echo sprintf('Goodbye %s!%s', $name, PHP_EOL);
},
]);
$howdy
->hello('Jim')
->goodbye('Joe');
https://3v4l.org/iIhph
You can do both adding methods and properties dynamically.
Properties:
class XXX
{
public function __construct($array1)
{
foreach ($array1 as $item) {
$this->$item = "PropValue for property : " . $item;
}
}
}
$a1 = array("prop1", "prop2", "prop3", "prop4");
$class1 = new XXX($a1);
echo $class1->prop1 . PHP_EOL;
echo $class1->prop2 . PHP_EOL;
echo $class1->prop3 . PHP_EOL;
echo $class1->prop4 . PHP_EOL;
Methods:
//using anounymous function
$method1 = function () {
echo "this can be in an include file and read inline." . PHP_EOL;
};
class class1
{
//build the new method from the constructor, not required to do it here by it is simpler.
public function __construct($functionName, $body)
{
$this->{$functionName} = $body;
}
public function __call($functionName, $arguments)
{
return call_user_func($this->{$functionName}, $arguments);
}
}
//pass the new method name and the refernce to the anounymous function
$myObjectWithNewMethod = new class1("method1", $method1);
$myObjectWithNewMethod->method1();
I've worked up the following code example and a helper method which works with __call which may prove useful. https://github.com/permanenttourist/helpers/tree/master/PHP/php_append_methods
How would I go about ensuring that the overridden parent method exists before I call it?
I've tried this:
public function func() {
if (function_exists('parent::func')) {
return parent::func();
}
}
However the function_exists never evaluates to true.
public function func()
{
if (is_callable('parent::func')) {
parent::func();
}
}
I use this for calling parent constructor if exists, works fine.
I also use the following as a generic version:
public static function callParentMethod(
$object,
$class,
$methodName,
array $args = []
) {
$parentClass = get_parent_class($class);
while ($parentClass) {
if (method_exists($parentClass, $methodName)) {
$parentMethod = new \ReflectionMethod($parentClass, $methodName);
return $parentMethod->invokeArgs($object, $args);
}
$parentClass = get_parent_class($parentClass);
}
}
use it like this:
callParentMethod($this, __CLASS__, __FUNCTION__, func_get_args());
The way to do that, is:
if (method_exists(get_parent_class($this), 'func')) {
// method exist
} else {
// doesn't
}
http://php.net/manual/en/function.method-exists.php
http://php.net/manual/en/function.get-parent-class.php
<?php
class super {
public function m() {}
}
class sub extends super {
public function m() {
$rc = new ReflectionClass(__CLASS__);
$namepc = $rc->getParentClass()->name;
return method_exists($namepc, __FUNCTION__);
}
}
$s = new sub;
var_dump($s->m());
gives bool(true). Not sure if this would work if the method was defined in a superclass of super, but it would be a matter of introducing a simple loop.