I'm to make a (sort of) factory class that accepts a variable number of arguments and passes them on to the class that it will be invoking
<?php
class A {
private $a;
private $b;
private $c;
public function __construct($a=1, $b=2, $c=3){
$this->a = $a;
$this->b = $b;
$this->c = $c;
}
}
class B {
private $foo;
public function __construct(){
$args = func_get_args();
$this->foo = call_user_func_array(array('A', '__construct'), $args);
}
public function getObject(){
return $this->foo;
}
}
$b = new B(10, 20, 30);
var_dump($b->getObject()); // should return equivalent of new A(10,20,30);
I'm getting this error
PHP Warning: call_user_func_array() expects parameter 1 to be a valid callback, non-static method A::__construct() cannot be called statically
Found this answer reading about ReflectionClass. This seems to work best
<?php
class Factory {
# ...
public function __construct(){
$args = func_get_args();
$a = new ReflectionClass('A');
$this->foo = $a->newInstanceArgs($args);
}
# ...
}
$class = "A";
$foo = new $class(1, 2, 56); // i think this not solve your problem
Or use ReflectionClass or maybe constructor injection with property injection.
I think you should solve the problem by not passing the values into the constructor, but rather by chaining.
class MyFactory() {
public $data;
public static function factory( $data = null ) {
return new MyFactory( $data );
}
public function addArgument( $argValue ) {
$this->data[] = $argValue;
return $this;
}
public function doSomeFunction() {
$data = $this->data; //is an array, so you can loop through and do whatever.
/* now your arbitrarily long array of data points can be used to do whatever. */
}
}
And you could use it like this:
$factory = MyFactory::factory();
$factory
->addArgument( '21' )
->addArgument( '903' )
->addArgument( '1' )
->addArgument( 'abc' )
->addArgument( 'jackson' )
->doSomeFunction();
I hope that at least gets you headed in a useful direction. You can do all sorts of crazy stuff with this type of pattern.
Try this, According to the php doc first example: http://us2.php.net/call_user_func_array
$this->foo = call_user_func_array(array(new A, '__construct'), $args);
Related
so a class:
class ToBeUsed
{
private $a;
public function setSomething($a)
{
$this->a = $a;
}
public function getSomething()
{
return $this->a;
}
}
its beign created and updated:
$obj = new ToBeUsed();
$obj->setSomething('a');
and passed to another object
class UseIt
{
/**
* #var ToBeUsed
*/
private $obj;
public function __construct(ToBeUsed $obj)
{
$this->obj = $obj;
}
public function work()
{
$this->obj->getSomething();
$this->obj->setSomething(); //// !!!!! THIS IS BAD!
}
}
now a classic DI example, except that the passed object should be "dulled" - only some methods are allowed to use. E.g. getSomething() is allowed to use, but setSomething() is not. What pattern / practice can get away with it? There used to be friend classes is C but its Php...
class ToBeUsed
{
private $a;
public function setSomething($a)
{
$dbg = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,2);
if(count($dbg) > 1){
return;
}
$this->a = $a;
}
public function getSomething()
{
return $this->a;
}
}
class UseIt
{
/**
* #var ToBeUsed
*/
private $obj;
public function __construct(ToBeUsed $obj)
{
$this->obj = $obj;
}
public function work()
{
echo $this->obj->getSomething().PHP_EOL; // a
$this->obj->setSomething('b'); // this does nothing
echo $this->obj->getSomething().PHP_EOL; // a
}
}
$obj = new ToBeUsed();
$obj->setSomething('a');
$obj2 = new UseIt($obj);
$obj2->work();
Alternatively, you can perform more complex checks on debug_backtrace() output.
I would probably do something with Interfaces, it doesn't prevent a method form being used. But "they" (whoever they is) would be using it outside of the Interface for $obj.
Like this:
class ToBeUsed implements ToBeUsedInterface
{
private $a;
public function getSomething()
{
return $this->a;
}
public function setSomething($a)
{
$this->a = $a;
}
}
interface ToBeUsedInterface{
public function getSomething();
}
class UseIt
{
/**
* #var ToBeUsed
*/
private $obj;
public function __construct(ToBeUsedInterface $obj)
{
$this->obj = $obj;
}
public function work()
{
$this->obj->getSomething();
$this->obj->setSomething(); //This now exists outside of the interface for $obj
}
}
In terms of IDE's this would prevent the methods from auto-completing as well.
The only other thing I can think of, ( besides the other answer ) would be to set the method to protected and then use ReflectionMethod to change the viability, when you want to use it.
Another Option, is Using Reflection
class ToBeUsed
{
private $a;
public function getSomething()
{
return $this->a;
}
protected function setSomething($a)
{
$this->a = $a;
}
}
$ToBeUsed = new ToBeUsed();
$ReflectionMethod = new ReflectionMethod($ToBeUsed, 'setSomething');
$ReflectionMethod->setAccessible(true);
$ReflectionMethod->invoke($ToBeUsed, 'foo');
echo $ToBeUsed->getSomething();
Outputs:
foo
You can see it live here
And Obviously sense it's protected under normal conditions, it could not be used inside UseIt. If I was going to use this for any amount of code, I would extend or wrap the Reflection class. Just to make the call a bit more concise, like this:
class MyReflector
{
public static function invoke($class, $method, ...$args)
{
$ReflectionMethod = new ReflectionMethod($class, $method);
$ReflectionMethod->setAccessible(true);
$ReflectionMethod->invokeArgs($class, $args);
}
}
$ToBeUsed = new ToBeUsed();
MyReflector::invoke($ToBeUsed,'setSomething', 'foo');
Please note I got all fancy with the variadic ...$arg which is for PHP 5.6+ it just lets you do
MyReflector::invoke($ToBeUsed,'setSomething', 'foo', 'bar');
And $args would be ['foo','bar'] in the first example it's just ['foo'] which can be used for invokeArgs for the second argument which takes an array of arguments to pass on to the actual method.
Does anyone know of an efficient technique in PHP to auto assign class parameters with identically named __construct() method arguments?
For instance, I've always thought it was highly inefficient to do something like the following:
<?php
class Foo
{
protected $bar;
protected $baz;
public function __construct($bar, $baz)
{
$this->bar = $bar;
$this->baz = $baz;
}
}
I'm wondering if there's a better/more efficient way/magic method to auto-assign class properties with identically named method parameters.
Thanks,
Steve
PHP 8
Constructor Promotion
function __construct(public $bar, public $baz) {}
PHP 5
function _promote(&$o) {
$m = debug_backtrace(0, 2)[1];
$ref = new ReflectionMethod($m['class'], $m['function']);
foreach($ref->getParameters() as $i=>$p) {
$o->{$p->name} = $m['args'][$i] ?? $p->getDefaultValue();
}
}
class Foo {
function __construct($bar, $baz) {
_promote($this);
}
}
I think this way is a pretty generally accepted way to do it, although you could make getters and setters. Or, if you're looking for magic methods in php: http://php.net/manual/en/language.oop5.magic.php
Not in a constructor. You can always wrap your properties into an array, instead, and only have a single property that needs to be set:
<?php
class SomeClass
{
protected $data = [];
public function __construct(array $data = [])
{
$this->data = $data;
}
public function getData()
{
return $this->data;
}
}
$params = ['bar' => 'bar', 'baz' => 'baz'];
$someClass = new SomeClass($params);
echo $someClass->getData()['bar'] . PHP_EOL;
There is the magic method __set, but it is only called when attempting to write data to inaccessible properties:
<?php
class SomeClass
{
protected $data = [];
public function __construct(array $data = [])
{
$this->data = $data;
}
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function __get($name)
{
if(isset($this->data[$name])) {
return $this->data[$name];
}
return null;
}
}
$class = new SomeClass;
$class->bar = 'bar';
$class->baz = 'baz';
echo $class->bar . PHP_EOL . $class->baz . PHP_EOL;
If your class is starting to have a lot of parameters being passed in to the constructor, it may be a sign that your class is getting too big and trying to do too much. A refactoring may be in order.
Question:
How to push $param from load() to $data property in class A?
Therefor I can use get_class_vars get_object_vars to load them.
Each time I use load function, it will add $param to $data property.
Example:
<?php
class Test {
public function testing($str) { echo $str; }
}
class A {
public $data = array();
public function load($param) {
array_push($this->data, $param); // not adding $param to $data property
return $param = new $param;
}
}
class B {
public $a;
public function __construct() {
$this->a = new A();
var_dump(get_object_vars($this->a)); // showing empty $data property
}
}
// Usage
$b = new B();
$test = $b->a->load('test');
$test->testing('hello');
Edit:
used get_object_vars($this->a)
get_class_vars only shows default public variables. Use get_object_vars($this->a) instead; this should work.
I am wondering if there is a way to attach a new method to a class at runtime, in php.
I mean, not on an instance level but directly to the class, so that all newly created instances, have this new method.
Can such a thing be done with reflection?
Thanks
Yes, you can.
Below is the way to create method in runtime in php 5.4.x.
The anonymous function is represented by Closure class started from 5.3.x. From 5.4.x, it add a Closure::bind static method to bind the anonymous function to a particular object or class.
Example:
class Foo {
private $methods = array();
public function addBar() {
$barFunc = function () {
var_dump($this->methods);
};
$this->methods['bar'] = \Closure::bind($barFunc, $this, get_class());
}
function __call($method, $args) {
if(is_callable($this->methods[$method]))
{
return call_user_func_array($this->methods[$method], $args);
}
}
}
$foo = new Foo;
$foo->addBar();
$foo->bar();
Did some playing around with whole thing. Seems that only thing you can potentially do with ReflectionClass is to replace an existing method. But even that would be indirectly.
I actually do not know any class-based language, where dynamic classes exist (then again, my knowledge is quite limited). I have seen it done only in prototype-based languages (javascript, ruby, smalltalk). Instead what you can do, in PHP 5.4, is to use Closure and add new methods to an existing object.
Here is a class which would let you perform such perversion to any object:
class Container
{
protected $target;
protected $className;
protected $methods = [];
public function __construct( $target )
{
$this->target = $target;
}
public function attach( $name, $method )
{
if ( !$this->className )
{
$this->className = get_class( $this->target );
}
$binded = Closure::bind( $method, $this->target, $this->className );
$this->methods[$name] = $binded;
}
public function __call( $name, $arguments )
{
if ( array_key_exists( $name, $this->methods ) )
{
return call_user_func_array( $this->methods[$name] , $arguments );
}
if ( method_exists( $this->target, $name ) )
{
return call_user_func_array(
array( $this->target, $name ),
$arguments
);
}
}
}
To use this, you have to provide constructor with an existing object. Here is small example of usage:
class Foo
{
private $bar = 'payload';
};
$foobar = new Foo;
// you initial object
$instance = new Container( $foobar );
$func = function ( $param )
{
return 'Get ' . $this->bar . ' and ' . $param;
};
$instance->attach('test', $func);
// setting up the whole thing
echo $instance->test('lorem ipsum');
// 'Get payload and lorem ipsum'
Not exactly what you want, but AFAIK this is as close you can get.
Have you taken a look at create_function() in the docs? You might also achieve the desired result by overloading.
This is possible with the runkit extension's runkit_method_add(). Be careful using this in production though.
Example:
<?php
class Example {}
$e = new Example();
runkit_method_add(
'Example',
'add',
'$num1, $num2',
'return $num1 + $num2;',
RUNKIT_ACC_PUBLIC
);
echo $e->add(12, 4);
You can use one of the below two methods also.
function method1()
{
echo "In method one.";
}
function method2()
{
echo "In method two.";
}
class DynamicClass
{
function __construct(){
$function_names = ['method1'];
foreach ($function_names as $function_name) {
if (function_exists($function_name)) {
$this->addMethod($function_name);
}
}
}
function addMethod($name)
{
$this->{$name} = Closure::fromCallable($name);
}
public function __call($name, $arguments)
{
return call_user_func($this->{$name}, $arguments);
}
}
$obj = new DynamicClass();
//Call method1 added in constructor
$obj->method1();
//Add method
$obj->addMethod('method2');
$obj->method2();
I've run into an odd problem and I'm not sure how to fix it. I have several classes that are all PHP implementations of JSON objects. Here' an illustration of the issue
class A
{
protected $a;
public function __construct()
{
$this->a = array( new B, new B );
}
public function __toString()
{
return json_encode( $this->a );
}
}
class B
{
protected $b = array( 'foo' => 'bar' );
public function __toString()
{
return json_encode( $this->b );
}
}
$a = new A();
echo $a;
The output from this is
[{},{}]
When the desired output is
[{"foo":"bar"},{"foo":"bar"}]
The problem is that I was relying on the __toString() hook to do my work for me. But it can't, because the serialize that json_encode() uses won't call __toString(). When it encounters a nested object it simply serializes public properties only.
So, the question then become this: Is there a way I can develop a managed interface to JSON classes that both lets me use setters and getters for properties, but also allows me to get the JSON serialization behavior I desire?
If that's not clear, here's an example of an implementation that won't work, since the __set() hook is only called for the initial assignment
class a
{
public function __set( $prop, $value )
{
echo __METHOD__, PHP_EOL;
$this->$prop = $value;
}
public function __toString()
{
return json_encode( $this );
}
}
$a = new a;
$a->foo = 'bar';
$a->foo = 'baz';
echo $a;
I suppose I could also do something like this
class a
{
public $foo;
public function setFoo( $value )
{
$this->foo = $value;
}
public function __toString()
{
return json_encode( $this );
}
}
$a = new a;
$a->setFoo( 'bar' );
echo $a;
But then I would have to rely on the diligence of the other developers to use the setters - I can't force adherence programmtically with this solution.
---> EDIT <---
Now with a test of Rob Elsner's response
<?php
class a implements IteratorAggregate
{
public $foo = 'bar';
protected $bar = 'baz';
public function getIterator()
{
echo __METHOD__;
}
}
echo json_encode( new a );
When you execute this, you can see that the getIterator() method isn't ever invoked.
A late answers but might be useful for others with the same problem.
In PHP < 5.4.0 json_encode doesn't call any method from the object. That is valid for getIterator, __serialize, etc...
In PHP > v5.4.0, however, a new interface was introduced, called JsonSerializable.
It basically controls the behaviour of the object when json_encode is called on that object.
Example:
class A implements JsonSerializable
{
protected $a = array();
public function __construct()
{
$this->a = array( new B, new B );
}
public function jsonSerialize()
{
return $this->a;
}
}
class B implements JsonSerializable
{
protected $b = array( 'foo' => 'bar' );
public function jsonSerialize()
{
return $this->b;
}
}
$foo = new A();
$json = json_encode($foo);
var_dump($json);
Outputs:
string(29) "[{"foo":"bar"},{"foo":"bar"}]"
Isn't your answer in the PHP docs for json_encode?
For anyone who has run into the problem of private properties not being added, you can simply implement the IteratorAggregate interface with the getIterator() method. Add the properties you want to be included in the output into an array in the getIterator() method and return it.
In PHP > v5.4.0 you can implement the interface called JsonSerializable as described in the answer by Tivie.
For those of us using PHP < 5.4.0 you can use a solution which employs get_object_vars() from within the object itself and then feeds those to json_encode(). That is what I have done in the following example, using the __toString() method, so that when I cast the object as a string, I get a JSON encoded representation.
Also included is an implementation of the IteratorAggregate interface, with its getIterator() method, so that we can iterate over the object properties as if they were an array.
<?php
class TestObject implements IteratorAggregate {
public $public = "foo";
protected $protected = "bar";
private $private = 1;
private $privateList = array("foo", "bar", "baz" => TRUE);
/**
* Retrieve the object as a JSON serialized string
*
* #return string
*/
public function __toString() {
$properties = $this->getAllProperties();
$json = json_encode(
$properties,
JSON_FORCE_OBJECT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
);
return $json;
}
/**
* Retrieve an external iterator
*
* #link http://php.net/manual/en/iteratoraggregate.getiterator.php
* #return \Traversable
* An instance of an object implementing \Traversable
*/
public function getIterator() {
$properties = $this->getAllProperties();
$iterator = new \ArrayIterator($properties);
return $iterator;
}
/**
* Get all the properties of the object
*
* #return array
*/
private function getAllProperties() {
$all_properties = get_object_vars($this);
$properties = array();
while (list ($full_name, $value) = each($all_properties)) {
$full_name_components = explode("\0", $full_name);
$property_name = array_pop($full_name_components);
if ($property_name && isset($value)) $properties[$property_name] = $value;
}
return $properties;
}
}
$o = new TestObject();
print "JSON STRING". PHP_EOL;
print "------" . PHP_EOL;
print strval($o) . PHP_EOL;
print PHP_EOL;
print "ITERATE PROPERTIES" . PHP_EOL;
print "-------" . PHP_EOL;
foreach ($o as $key => $val) print "$key -> $val" . PHP_EOL;
print PHP_EOL;
?>
This code produces the following output:
JSON STRING
------
{"public":"foo","protected":"bar","private":1,"privateList":{"0":"foo","1":"bar","baz":true}}
ITERATE PROPERTIES
-------
public -> foo
protected -> bar
private -> 1
privateList -> Array
Even if your protected variable was public instead of protected, you won't have the desired input since this will output the entire object like this:
[{"b":{"foo":"bar"}},{"b":{"foo":"bar"}}]
Instead of:
[{"foo":"bar"},{"foo":"bar"}]
It will most likely defeat your purpose, but i'm more inclined to convert to json in the original class with a default getter and calling for the values directly
class B
{
protected $b = array( 'foo' => 'bar' );
public function __get($name)
{
return json_encode( $this->$name );
}
}
Then you could do with them whatever you desire, even nesting the values in an additional array like your class A does, but using json_decode.. it still feels somewhat dirty, but works.
class A
{
protected $a;
public function __construct()
{
$b1 = new B;
$b2 = new B;
$this->a = array( json_decode($b1->b), json_decode($b2->b) );
}
public function __toString()
{
return json_encode( $this->a );
}
}
In the documentation there are some responses to this problem (even if i don't like most of them, serializing + stripping the properties makes me feel dirty).
You're right the __toString() for the class B is not being called, because there is no reason to. So to call it, you can use a cast
class A
{
protected $a;
public function __construct()
{
$this->a = array( (string)new B, (string)new B );
}
public function __toString()
{
return json_encode( $this->a );
}
}
Note: the (string) cast before the new B's ... this will call the _toString() method of the B class, but it won't get you what you want, because you will run into the classic "double encoding" problems, because the array is encoded in the B class _toString() method, and it will be encoded again in the A class _toString() method.
So there is a choice of decoding the result after the cast, ie:
$this->a = array( json_decode((string)new B), json_decode((string)new B) );
or you're going to need to get the array, by creating a toArray() method in the B class that returns the straight array. Which will add some code to the line above because you can't use a PHP constructor directly (you can't do a new B()->toArray(); ) So you could have something like:
$b1 = new B;
$b2 = new B;
$this->a = array( $b1->toArray(), $b2->toArray() );
A function to prepare data for json_encode that uses __toString() if available
This is useful if you don't have control over the classes / objects.
/**
* Prepares data for encoding by replacing any object in the input with output of its get_data()
* or __toString() method if available.
*
* #param mixed $data .
* #return mixed
*/
function prepare_for_encoding($data) {
if (is_array($data)) {
foreach ($data as $i => $value) {
$data[$i] = prepare_for_encoding($data[$i]);
}
} elseif (is_object($data)) {
if (method_exists($data, 'get_data')) {
$data = $data->get_data();
} elseif (method_exists($data, '__toString')) {
$data = $data->__toString();
// Sometimes toString() may return encoded json data - see if it is decodeable so it can
// be encoded together with the entire structure.
$decoded = json_decode($data, true);
if ($decoded !== null) {
$data = $decoded;
}
}
}
return $data;
}
Usage:
class Foo {
public $a = 1;
private $b= 2;
public __toString() {
return json_encode(array(
'a' => $this->a,
'b' => $this->b,
}
}
$foo = new Foo();
json_encode(prepare_for_encoding($foo));
json_encode(prepare_for_encoding(array('foo' => $foo));