I am having a class and I have a dynamically created function (created via "create_function") but I cannot find a way to tell PHP that I want this function to be created for this class only (class function) and because of that the new function cannot access the object properties. Take a look at the following code:
class Test {
private $var=1;
function __construct() {
call_user_func(create_function('', 'echo $this->var;'));
}
}
new Test;
This throws error "Fatal error: Using $this when not in object context in D:\WWW\index.php(7) : runtime-created function on line 1"
You probably want runkit_method_add, not create_function.
As of php 5.4 anonymus function also have $this in their context. With a little help from the magic _call method it is possible to add a closure as a method to a class, without additional code:
class Test
{
private $var = 1;
function __construct()
{
$this->sayVar = function() { echo $this->var; };
}
public function __call( $method, $args )
{
if ( property_exists( $this, $method ) ) {
if ( is_callable( $this->$method ) ) {
return call_user_func_array( $this->$method, $args );
}
}
}
}
$test = new Test();
$test->sayVar(); // echos 1
Related
I have to classes and I want to pass a method fro classA to classB constructor and store it on a classB instance variable so as to execute it later.
class A
{
public function execute()
{
$ClassB = new ElgEmpAnalyticsImporterControllerImporterEmporioOrder(ConMW::getDB(), $this -> lPointFile, [$this, 'getLastPoints'] );
$ClassB -> import();
}
public function getLastPoints(Array $keys)
{
$res = [];
forEach ( json_decode( file_get_contents($this -> lastPointFile) ) as $key => $value ):
if ( in_array($key, $keys) ):
$res[$key] = $value;
else:
$res[$key] = '';
endif;
endforeach;
unset($key);
unset($value);
return $res;
}
}
classB
{
public $getLastPoints = null;
public function __construct(callable $getLastPoints)
{
$this -> getLastPoints = $getLastPoints;
}
public function import()
{
$lastPoints = $this -> getLastPoints(['idOrder', 'orderLastExport']);
}
}
Trying to execute it like that I get the error "Call to undefined method getLastPoints()"
I think the problem is on storing the function on the instance variable $getLastPoints of classB. I can conclude this because If I execute the function on the constructor it works. That means if I change the constructor of classB like this
classB
{
public $getLastPoints = null;
public function __construct(callable $getLastPoints)
{
$getLastPoints(['idOrder', 'orderLastExport']);
}
}
it works.
But what i need is to execute the external function inside the import function.
Can someone please help me?
thanks for your time,
Edit for clarification: My question is why I can execute the function inside the contructor like this :
$lastPoint(a,b)
but when I assign the callable into an instance variable like this:
$this -> lastPoint(a,b)
it does not work.
I read that php uses different storage for variables and function. PHP probably sees the callable $lastPoints as a variable. So can the callable $lastPoints, be added as dynamic function to my instance of classB?
Christoforos
PHP Callable Object as Object Member
$getlastpoints is a property with an array value stored in it, not a function.
call_user_func_array($this->getlastpoints, ['idOrder', 'orderLastExport']);
public function import()
{
$my_func = $this->getLastPoints;
$lastPoints = $my_func(['idOrder', 'orderLastExport']);
}
In a nutshell, the reason you will have to do this is because you can define properties and methods in a PHP class having the same name. e.g.
class foo {
public $bar
public function bar() {}
}
so in this instance, if allowed to directly access a stored callable on the $bar property... What would the call below reference?
$my_foo_obj->bar()
To avoid the situation, you cannot call it directly.
Is it possible to add a method/function in this way, like
$arr = array(
"nid"=> 20,
"title" => "Something",
"value" => "Something else",
"my_method" => function($arg){....}
);
or maybe like this
$node = (object) $arr;
$node->my_method=function($arg){...};
and if it's possible then how can I use that function/method?
This is now possible to achieve in PHP 7.1 with anonymous classes
$node = new class {
public $property;
public function myMethod($arg) {
...
}
};
// and access them,
$node->property;
$node->myMethod('arg');
You cannot dynamically add a method to the stdClass and execute it in the normal fashion. However, there are a few things you can do.
In your first example, you're creating a closure. You can execute that closure by issuing the command:
$arr['my_method']('Argument')
You can create a stdClass object and assign a closure to one of its properties, but due to a syntax conflict, you cannot directly execute it. Instead, you would have to do something like:
$node = new stdClass();
$node->method = function($arg) { ... }
$func = $node->method;
$func('Argument');
Attempting
$node->method('Argument')
would generate an error, because no method "method" exists on a stdClass.
See this SO answer for some slick hackery using the magic method __call.
Since PHP 7 it is also possible to directly invoke an anonymous function property:
$obj = new stdClass;
$obj->printMessage = function($message) { echo $message . "\n"; };
echo ($obj->printMessage)('Hello World'); // Hello World
Here the expression $obj->printMessage results in the anonymous function which is then directly executed with the argument 'Hello World'. It is however necessary to put the function expression in paranetheses before invoking it so the following will still fail:
echo $obj->printMessage('Hello World');
// Fatal error: Uncaught Error: Call to undefined method stdClass::printMessage()
Another solution would be to create an anonymous class and proxy the call via the magic function __call, with arrow functions you can even keep reference to context variables:
new Class ((new ReflectionClass("MyClass"))->getProperty("myProperty")) {
public function __construct(ReflectionProperty $ref)
{
$this->setAccessible = fn($o) => $ref->setAccessible($o);
$this->isInitialized = fn($o) => $ref->isInitialized($o);
$this->getValue = fn($o) => $ref->getValue($o);
}
public function __call($name, $arguments)
{
$fn = $this->$name;
return $fn(...$arguments);
}
}
class myclass {
function __call($method, $args) {
if (isset($this->$method)) {
$func = $this->$method;
return call_user_func_array($func, $args);
}
}
}
$obj = new myclass();
$obj->method = function($var) { echo $var; };
$obj->method('a');
Or you can create defult class and use...
I am trying to use an instance method as a callback for PHP 5.2.1. I am aware that as of PHP 5.4 you can use $this inside a closure, and in PHP 5.3 you can rename $this to $self and pass that to the closure. However, neither of these methods will suffice since I need this to work for PHP 5.2.1. The two commented lines was my last attempt. That results in Fatal error: Call to a member function hello() on a non-object - is there anyway I can have a callback to an instance method in PHP 5.2.1?
<?php
class Test {
public function __construct() {
$self = &$this;
$cb = function() use ( $self ) {
$self->hello();
};
call_user_func( $cb );
// $cb = create_function( '$self', '$self->hello();' );
// call_user_func( $cb );
}
public function hello() {
echo "Hello, World!\n";
}
}
$t = new Test();
Pass an array to include the object:
call_user_func( array( $this, 'hello' ) );
http://us3.php.net/manual/en/language.types.callable.php
$cb = create_function('$self', '$self->hello();');
This is just making a function that can take a parameter called $self. It's the same as this:
function test($self){
$self->hello();
}
You can try passing $self (or $this) to the function when you call it:
call_user_func($cb, $this);
You can also try to make $self a global variable, so that the anonymous function made by create_function can read it.
$GLOBALS['mySelf'] = $self;
$cb = create_function('', 'global $mySelf; $mySelf->hello();');
call_user_func($cb);
// You may want to unset this when done
unset($GLOBALS['mySelf']);
How about SIMPLICITY?
class Test {
public function __construct() {
$this -> funcName($this);
}
public function funcName($obj) {
$obj->hello();
}
public function hello() {
echo "Hello, World!\n";
}
}
Update: Just tested the codes. They are working fine using this.
call_user_func_array(array($self, "hello"), array());
I want to write a sort of "plugin/module" system for my code, and it would make it much easier if I could "add" stuff into a class after it's been defined.
For example, something like this:
class foo {
public function a() {
return 'b';
}
}
There's the class. Now I want to add another function/variable/const to it, after it's defined.
I realize that this is probably not possible, but I need confirmation.
No, you cannot add methods to an already defined class at runtime.
But you can create similar functionality using __call/__callStatic magic methods.
Class Extendable {
private $handlers = array();
public function registerHandler($handler) {
$this->handlers[] = $handler;
}
public function __call($method, $arguments) {
foreach ($this->handlers as $handler) {
if (method_exists($handler, $method)) {
return call_user_func_array(
array($handler, $method),
$arguments
);
}
}
}
}
Class myclass extends Extendable {
public function foo() {
echo 'foo';
}
}
CLass myclass2 {
public function bar() {
echo 'bar';
}
}
$myclass = new myclass();
$myclass->registerHandler(new myclass2());
$myclass->foo(); // prints 'foo'
echo "\n";
$myclass->bar(); // prints 'bar'
echo "\n";
This solution is quite limited but maybe it will work for you
To add/change how classes behave at runtime, you should use Decorators and/or Strategies. This is the prefered OO approach over resorting to any magic approaches or monkey patching.
A Decorator wraps an instance of a class and provides the same API as that instance. Any calls are delegated to the wrapped instance and results are modified where needed.
class Decorator
{
// …
public function __construct($decoratedInstance)
{
$this->_decoratedInstace = $decoratedInstance;
}
public function someMethod()
{
// call original method
$result = $this->_decoratedInstance->someMethod();
// decorate and return
return $result * 10;
}
// …
}
For Strategy, see my more complete example at Can I include code into a PHP class?
More details and example code can be found at
http://sourcemaking.com/design_patterns/decorator
http://sourcemaking.com/design_patterns/strategy
I have a few methods for you to try. :) Have fun coding.
Method for only one class:
class main_class {
private $_MODS = array(),...;
public ...;
public function __construct(...) {
...
global $MODS_ENABLED;
$this -> $_MODS = $MODS_ENABLED;
}
...
public function __get( $var ) {
foreach ( $this->_MODS as $mod )
if ( property_exists( $mod, $var ) )
return $mod -> $var;
}
public function __call( $method, $args ) {
foreach ( $this->_MODS as $mod )
if ( method_exists( $mod, $method ) )
return call_user_method_array( $method, $mod, $args );
}
}
Method for when you want to deal with more than one class:
class modMe {
private $_MODS = array();
public function __construct__() {
global $MODS_ENABLED;
$this -> $_MODS = $MODS_ENABLED;
}
public function __get( $var ) {
foreach ( $this->_MODS as $mod )
if ( property_exists( $mod, $var ) )
return $mod -> $var;
}
public function __call( $method, $args ) {
foreach ( $this->_MODS as $mod )
if ( method_exists( $mod, $method ) )
return call_user_method_array( $method, $mod, $args );
}
}
class mainClass extends modMe {
function __construct(...){
$this -> __construct__();
}
}
Now lets try to use them:
$MODS_ENABLED = array();
$MODS_ENABLED[] = new mod_mail();
$myObj = new main_class(...);
$myObj -> mail("me#me.me","you#you.you","subject","message/body","Extra:Headers;More:Headers");
# Hey look, my mail class was just added into my main_class for later use.
Note:
I am currently using the first method (I only have one class, the mods are exceptions) in my own CMS that I have made from scratch (http://sitegen.com.au), and it works great, my reason on needing this is because I have my main_class that is getting generated after I have required all mods in ./mods-enabled/* creating functions and changing how other functions work, I will also come back here another time with a solution for two mods to both change a function without one winning as it ran first. I have split my plugins in two, mods that run on every site, and plugins that have settings for a site and may not even be enabled.
Have fun programming.
You can extend the class
class foo {
public function a() {
return 'b';
}
}
class woo extends foo {
public function newStuff() {
$var = $this->a();
echo $var;
}
}
By extending foo from the woo class the functionality in foo is usable while you can also create new methods in woo. That's the easiest way to add new functionality to a class.
You can use magic functionality of PHP to provide actions on methods that are not defined at compile time.
It is actually possible. For instance:
<?php
class Test {
function set($set, $val) {
$this->$set = $val;
}
function get($get) {
return $this->$get;
}
}
$t = new Test();
$t->set('hello', 'world');
echo $t->get('hello');
exit;
?>
If it is not enough magic for you, you can use dynamic objects. The common idea is here: https://github.com/ptrofimov/jslikeobject
I was writing a class that uses __get() and __set() to store and retrieve array elements in a master array. I had a check to make some elements ungettable, basically to re-create private properties.
I noticed that it seemed that __get intercepts all calls to class properties. This sucks for me, because I wanted to have a variable private to the outside world ( unavailable via get ), but I was trying to access it by directly referencing the master array from within the class. Of course, the master array is not in the whitelist of gettable properties :(
Is there a way I can emulate public and private properties in a php class that uses __get() and __set()?
Example:
<?
abstract class abstraction {
private $arrSettables;
private $arrGettables;
private $arrPropertyValues;
private $arrProperties;
private $blnExists = FALSE;
public function __construct( $arrPropertyValues, $arrSettables, $arrGettables ) {
$this->arrProperties = array_keys($arrPropertyValues);
$this->arrPropertyValues = $arrPropertyValues;
$this->arrSettables = $arrSettables;
$this->arrGettables = $arrGettables;
}
public function __get( $var ) {
echo "__get()ing:\n";
if ( ! in_array($var, $this->arrGettables) ) {
throw new Exception("$var is not accessible.");
}
return $this->arrPropertyValues[$var];
}
public function __set( $val, $var ) {
echo "__set()ing:\n";
if ( ! in_array($this->arrSettables, $var) ) {
throw new Exception("$var is not settable.");
}
return $this->arrPropertyValues[$var];
}
} // end class declaration
class concrete extends abstraction {
public function __construct( $arrPropertyValues, $arrSettables, $arrGettables ) {
parent::__construct( $arrPropertyValues, $arrSettables, $arrGettables );
}
public function runTest() {
echo "Accessing array directly:\n";
$this->arrPropertyValues['color'] = "red";
echo "Color is {$this->arrPropertyValues['color']}.\n";
echo "Referencing property:\n";
echo "Color is {$this->color}.\n";
$this->color = "blue";
echo "Color is {$this->color}.\n";
$rand = "a" . mt_rand(0,10000000);
$this->$rand = "Here is a random value";
echo "'$rand' is {$this->$rand}.\n";
}
}
try {
$objBlock = & new concrete( array("color"=>"green"), array("color"), array("color") );
$objBlock->runTest();
} catch ( exception $e ) {
echo "Caught Exeption $e./n/n";
}
// no terminating delimiter
$ php test.php
Accessing array directly:
__get()ing:
Caught Exeption exception 'Exception' with message 'arrPropertyValues is not accessible.' in /var/www/test.php:23
Stack trace:
#0 /var/www/test.php(50): abstraction->__get('arrPropertyValu...')
#1 /var/www//test.php(68): concrete->runTest()
#2 {main}.
Is there a way I can emulate public and private properties in a php class that uses __get() and __set()?
Not directly (if you discount debug_backtrace).
But you can have a private method getPriv that does all the work your current __get does. Then __get would only wrap this private method and check accessibility.
function __get($name) {
if (in_array($name, $this->privateProperties))
throw new Exception("The property ". __CLASS__ . "::$name is private.");
return $this->getPriv($name);
}
Inside your class, you would call getPriv, thus bypassing __get.
Make abstraction::$arrPropertyValues protected or do what Artefacto wrote (if you need additional checks), except that abstraction::getPriv() should be protected.
Rather than manually enlisting private/protected properties, you could use PHPs cumbersome reflection methods:
function __get($name) {
$reflect = new ReflectionObject($this);
$publics = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);
if (in_array($name, $publics)) {
return $this->{$name};
}
}