This question already has an answer here:
How to call the constructor with call_user_func_array in PHP
(1 answer)
Closed 7 years ago.
I have searched many a page of Google results as well as here on stackoverflow but cannot find a solution that seems to fit my situation. I appear to have but one last snag in the function I am trying to build, which uses call_user_func_array to dynamically create objects.
The catchable fatal error I am getting is Object of class Product could not be converted to string. When the error occurs, in the log I get five of these (one for each argument): PHP Warning: Missing argument 1 for Product::__construct(), before the catchable fatal error.
This is the code of the function:
public static function SelectAll($class, $table, $sort_field, $sort_order = "ASC")
{
/* First, the function performs a MySQL query using the provided arguments. */
$query = "SELECT * FROM " .$table. " ORDER BY " .$sort_field. " " .$sort_order;
$result = mysql_query($query);
/* Next, the function dynamically gathers the appropriate number and names of properties. */
$num_fields = mysql_num_fields($result);
for($i=0; $i < ($num_fields); $i++)
{
$fetch = mysql_fetch_field($result, $i);
$properties[$i] = $fetch->name;
}
/* Finally, the function produces and returns an array of constructed objects.*/
while($row = mysql_fetch_assoc($result))
{
for($i=0; $i < ($num_fields); $i++)
{
$args[$i] = $row[$properties[$i]];
}
$array[] = call_user_func_array (new $class, $args);
}
return $array;
}
Now, if I comment out the call_user_func_array line and replace it with this:
$array[] = new $class($args[0],$args[1],$args[2],$args[3],$args[4]);
The page loads as it should, and populates the table I am building. So everything is absolutely functional until I try to actually use my $args array within call_user_func_array.
Is there some subtle detail about calling that array that I am missing? I read the PHP manual for call_user_func_array once, and then some, and examples on that page seemed to show people just building an array and calling it for the second argument. What could I be doing wrong?
You can't call the constructor of $class like this:
call_user_func_array (new $class, $args);
That's no valid callback as first parameter. Let's pick this apart:
call_user_func_array (new $class, $args);
Is the same as
$obj = new $class;
call_user_func_array ($obj, $args);
As you can see, the constructor of $class has been already called before call_user_func_array comes into action. As it has no parameters, you see this error message:
Missing argument 1 for Product::__construct()
Next to that, $obj is of type object. A valid callback must be either a string or an array (or exceptionally a very special object: Closure, but that's out of discussion here, I only name it for completeness).
As $obj is an object and not a valid callback, so you see the PHP error message:
Object of class Product could not be converted to string.
PHP tries to convert the object to string, which it does not allow.
So as you can see, you can't easily create a callback for a constructor, as the object yet not exists. Perhaps that's why you were not able to look it up in the manual easily.
Constructors need some special dealing here: If you need to pass variable arguments to a class constructor of a not-yet initialize object, you can use the ReflectionClass to do this:
$ref = new ReflectionClass($class);
$new = $ref->newInstanceArgs($args);
See ReflectionClass::newInstanceArgs
Not possible using call_user_func_array(), because (as the name suggest) it calls functions/methods, but is not intended to create objects, Use ReflectionClass
$refClass = new ReflectionClass($class);
$object = $refClass->newInstanceArgs($args);
Another (more design-based) solution is a static factory method
class MyClass () {
public static function create ($args) {
return new self($args[0],$args[1],$args[2],$args[3],$args[4]);
}
}
and then just
$object = $class::create($args);
In my eyes it's cleaner, because less magic and more control
I use this for singleton factory pattern, becouse the ReflectionClass brokes the dependence tree, I hate the use of eval but its the only way to i find to simplificate the use of singleton pattern to inject mockObjects whith PHPUnit whitout open the class methods to that injection, BE CAREFULL WHITH THE DATA WHAT YOU PASS TO eval FUNCTION!!!!!!!! YOU MUST BE SURE THAT IS CLEANED AND FILTERED!!!
abstract class Singleton{
private static $instance=array();//collection of singleton objects instances
protected function __construct(){}//to allow call to extended constructor only from dependence tree
private function __clone(){}//to disallow duplicate
private function __wakeup(){}//comment this if you want to mock the object whith php unit jejeje
//AND HERE WE GO!!!
public static function getInstance(){
$a=get_called_class();
if(!array_key_exists($a, self::$instance)){
if(func_num_args()){
/**HERE IS THE CODE **//
$args=func_get_args();
$str='self::$instance[$a]=new $a(';
for($i=0;$i<count($args);$i++){
$str.=(($i)?",":"").'$args['.$i.']';
}
eval($str.");");//DANGER, BE CAREFULLY...we only use this code to inject MockObjects in testing...to another use you will use a normal method to configure the SingletonObject
/*--------------------------*/
}else{
self::$instance[$a]=new $a();
}
}
return self::$instance[$a];
}
}
And to use that:
class MyClass extends Singleton{
protected function __construct(MyDependInjection $injection){
//here i use the args like a normal class but the method IS PROTECTED!!!
}
}
to instanciate the object:
$myVar= MyClass::getInstance($objetFromClassMyDependInjection);
it calls the constructor whith the args I pased. i know that i can get the same result extending the static method getInstance but to teamworking its more easy to use this way
Related
I've built a singleton class with chaining methods (to be used in a template).
To make chaining work I need to return new static. It allows the next chain to be added. The problem I have is that I don't want to return the static object if there are no more chains.
Example
<?php
class bread {
public static $array;
public static function blueprints() {
static::$array = array('some', 'values');
return new static;
}
public static function fields() {
return static::$array;
}
}
$blueprints = bread::blueprints();
$fields = bread::blueprints()->fields();
print_r($blueprint) // Returns object - FAIL
print_r($fields ) // Returns array - OK
In the example above I want $blueprints to return an array, because there are no more methods chained on it.
How can that be done?
The simple answer is you cannot do what you want.
Method chaining is not a special thing for Php.
For your example
bread::blueprints()->fields();
This is not different than:
$tmp = bread::blueprints();
$tmp->fields();
So because of the Php does not know the context where the result will be used of it cannot change the return type.
Here is another version of this question:
Check if call is method chaining
However, your class can implement ArrayAccess interface.This will allow you to treat the object like an array without casting and you get total control over how the members are used.
You can try this:
$blueprints = (array)bread::blueprints();
I have a constructor that asks for a type of class, but it doesn't define that as a type hint. You are able to pass anything you want to it, and it will accept it. Is there a way to pass a class type to the constructor, and in the add() method it only accepts that type?
Currently what I have, is the ability to pass anything to the constructor such as an int, string, bool, etc. Is there a way to make it so that the constructor only accepts class types?
class Main{
protected $items = [];
protected $type = '';
public function __construct($type){
$this->type = $type;
}
public function add($object){
if($object instanceof $this->type){
$this->items[] = $object;
}
}
}
class Test{}
class Awesome{}
$main1 = new Main(Test::class);
$main2 = new Main(Awesome::class);
// Successful:
$main1->add(new Test());
// Fail:
$main1->add(new Awesome());
// Successful:
$main2->add(new Awesome());
// Fail:
$main2->add(new Test());
If I were to do it in C# it would look something like this:
Main main1 = new Main<Test>();
Main main2 = new Main<Awesome>();
Basically it says that add() will only allow instances of Test. Is there a way to do some
Php doesn't support template like declarations like e.g. c++.
The best way you may be able to achive this is by passing a lambda which then in return gets used in order to validate the passed parameter in add.
<?php
class Test {
private $validator = null;
public function __construct($validator) {
$this->validator = $validator;
}
public function add($value) {
$func = $this->validator;
$validated = $func($value);
echo $validated ? 'OK' : 'NG';
}
}
$obj = new Test(function($value) {
return is_int($value);
});
$obj->add(11);
$obj->add('string');
Another possibility would be to pass the type e.g. "ClassName" in your constructor and use get_class() and gettype() for the validation.
In the future there may be smarter solutions since you'll be able to write anonymous classes but I haven't really thought about that but in the end they would work similarly to lambdas.
Basically it says that add() will only allow instances of Test.
It's possible to achieve this in PHP by simply adding the type before the argument name in the function definition (similar with C/C++/C# types):
class Main {
protected $items = [];
public function add(Test $object) {
$this->items[] = $object;
}
}
PHP 5 accepts classes, interfaces, array and callable as type hints. If Test is a class then Main::add() accepts objects of class Test and its children. If Test is an interface, then the method Main::add() accepts objects that implement Test or one of its children.
PHP 7 (coming soon to a server near you) introduces type hinting for scalar types too.
PHP does not support anything similar with C++ templates or C# generics. If you want to create a class that works with objects of type A and another class that has identical behaviour but works with objects of type B you have several options but none of them is as elegant as the templates/generics:
Create two classes having identical behaviour, one for objects of type A, another for objects of type B; use different type hints (A and B) in the arguments lists of the methods of the two classes to enforce the separation - not scalable;
Something similar to your code, use the allowed class name as a string property and check it on any operation; you can also validate the argument of the constructor using class_exists() - the code becomes cluttered with tests and less readable;
Use OOP polymorphism; extend both A and B from the same class T or, even better, make A and B implement the same interface I. A PHP interface can be empty, it doesn't need to declare anything; empty interfaces used just for type hinting are common practice in PHP.
Then write a single class Main and use I as type hint for all its methods that accept objects. It will accept objects of both types A and B but if you also declare functions in I (and implement them in A and B, of course) then use them in Main you can be sure nothing breaks (I becomes a contract between Main and the objects its accepts as arguments for its methods).
I would choose option #3 because it gets the most help from the interpreter; it verifies the type of the arguments on each function call that has type hints and triggers a recoverable fatal error (in PHP 5) or throws an exception (in PHP 7).
Also some IDEs and static code analysis tools can validate the calls without running the code and help you fix it.
Is there a way to make it so that the constructor only accepts class
types?
Nope!
It is not possible in PHP. Not like C#, at least.
You need either set a type hint or set any types.
However, there's a closer solution in order to accept only class when instancing a class: Using ReflectionClass!
class Main {
protected $items = [];
protected $type = null;
public function __construct($type) {
$reflector = new ReflectionClass($type);
$this->type = $reflector->getName(); # or: $this->type = $type;
}
public function add($object) {
if($object instanceof $this->type) {
$this->items[] = $object;
}
}
}
As ReflectionClass contructor argument only accpets a string containing the name of the class to reflect, you can take advantage that, so passing scalars strings will cause an exception.
$main = new Main(Test::class); # Okay!
$main = new Main('Test'); # Okay!
However
$main = new Main('bool');
// Results
# PHP Fatal error: Uncaught exception 'ReflectionException'
# with message 'Class bool does not exist' in ...
Change your constructor to this:
public function __construct(Type $type){
$this->type = $type;
}
This is based on the assumption that $type is an instance of Type.
I try to create new object of a class called Isolate (this class is used to prevent XSS and other attacks via htmlspecialchars and so on).
So, I do it like this:
$data['name'] = $_POST['name'];
$data = $isolate->isolateArr($data);
And my Isolate class look like this:
class Isolate {
public function isolate($var) {
$iVar = htmlspecialchars($var);
$iVar = mysql_real_escape_string($iVar);
$iVar = stripcslashes($iVar);
return $iVar;
}
public function isolateArr($arr) {
foreach($arr as &$instance) {
$instance = $this->isolate($instance);
}
unset($instance);
return $arr;
}
But as the result I have a warning like Missing argument 1 for Isolate. As I understand, it asks me for the argument for the first function, but I do not need to call the first one, I need to call the second (because I have an array in this case).
So, why does it always ask for the first function argument? There in no any __construct method, what is the point?
Thank you in advance!
isolate() is your constructor method.
http://www.php.net/manual/en/language.oop5.decon.php
For backwards compatibility, if PHP 5 cannot find a __construct() function for a given class, and the class did not inherit one from a parent class, it will search for the old-style constructor function, by the name of the class.
Assume this class code:
class Foo {
function method() {
echo 'works';
}
}
Is there any way to store a reference to the method method of a Foo instance?
I'm just experimenting and fiddling around, my goal is checking whether PHP allows to call $FooInstance->method() without writing $FooInstance-> every time. I know I could write a function wrapper for this, but I'm more interested in getting a reference to the instance method.
For example, this pseudo-code would theoretically store $foo->method in the $method variable:
$foo = new Foo();
$method = $foo->method; //Undefined property: Foo::$method
$method();
Apparently, as method is a method and I'm not calling it with () the interpreter thinks I'm looking for a property thus this doesn't work.
I've read through Returning References but the examples only show how to return references to variables, not methods.
Therefore, I've adapted my code to store an anonymous function in a variable and return it:
class Foo {
function &method() {
$fn = function() {
echo 'works';
};
return $fn;
}
}
$foo = new Foo();
$method = &$foo->method();
$method();
This works, but is rather ugly. Also, there's no neat way to call it a single time, as this seems to require storing the returned function in a variable prior to calling it: $foo->method()(); and ($foo->method())(); are syntax errors.
Also, I've tried returning the anonymous function directly without storing it in a variable, but then I get the following notice:
Notice: Only variable references should be returned by reference
Does this mean that returning/storing a reference to a class instance method is impossible/discouraged or am I overlooking something?
Update: I don't mind adding a getter if necessary, the goal is just getting a reference to the method. I've even tried:
class Foo {
var $fn = function() {
echo 'works';
};
function &method() {
return $this->fn;
}
}
But from the unexpected 'function' (T_FUNCTION) error I'd believe that PHP wisely doesn't allow properties to store functions.
I'm starting to believe that my goal isn't easily achievable without the use of ugly hacks as eval().
It is. You have to use an array, with two values: the class instance (or string of the class name if you are calling a static method) and the method name as a string. This is documented on the Callbacks Man page:
A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1.
Demo (Codepad):
<?php
class Something {
public function abc() {
echo 'called';
}
}
$some = new Something;
$meth = array($some, 'abc');
$meth(); // 'called'
Note this is also works with the built-ins that require callbacks (Codepad):
class Filter {
public function doFilter($value) {
return $value !== 3;
}
}
$filter = new Filter;
$test = array(1,2,3,4,5);
var_dump(array_filter($test, array($filter, 'doFilter'))); // 'array(1,2,4,5)'
And for static methods -- note the 'Filter' instead of an instance of a class as the first element in the array (Codepad):
class Filter {
public static function doFilter($value) {
return $value !== 3;
}
}
$test = array(1,2,3,4,5);
var_dump(array_filter($test, array('Filter', 'doFilter'))); // 'array(1,2,4,5)'
// -------- or -----------
var_dump(array_filter($test, 'Filter::doFilter')); // As of PHP 5.2.3
Yes, you can. PHP has a "callable" pseudo-type, which is, in fact, either just a string or an array. Several functions (usort comes to mind) accept a parameter of the "callback" type: in fact, they just want a function name, or an object-method pair.
That's right, strings are callable:
$fn = "strlen";
$fn("string"); // returns 6
As mentioned, it's possible to use an array as a callback, too. In that case, the first element has to be an object, and the second argument must be a method name:
$obj = new Foo();
$fn = array($obj, "method");
$fn(); // calls $obj->method()
Previously, you had to use call_user_func to call them, but syntax sugar in recent versions make it possible to perform the call straight on variables.
You can read more on the "callable" documentation page.
No, as far as I know it's not possible to store a reference to a method in PHP. Storing object / class name and a method name in an array works, but it's just an array without any special meaning. You can play with the array as you please, for example:
$ref = [new My_Class(), "x"];
// all is fine here ...
$ref();
// but this also valid, now the 'reference' points to My_Other_Class::x()
// do you expect real reference to behave like this?
$ref[0] = new My_Other_Class();
$ref();
// this is also valid syntax, but it throws fatal error
$ref[0] = 1;
$ref();
// let's assume My_Class::y() is a protected method, this won't work outside My_Class
$ref = [new My_Class(), 'y'];
$ref();
this is prone to error as you loose syntax checking due to storing the method name as string.
you can't pass reliably a reference to a private or a protected method this way (unless you call the reference from a context that already has proper access to the method).
Personally I prefer to use lambdas:
$ref = function() use($my_object) { $my_object->x(); }
If you do this from inside $my_object it gets less clunky thanks to access to $this:
$ref = function() { $this->x(); }
this works with protected / private methods
syntax checking works in IDE (less bugs)
unfortunately it's less concise
Quick one:
Is there any way to enforce types for variadic functions in PHP? I'm assuming not, however maybe I've missed something.
As of now, I'm just forcing a single required argument of the needed type, and iterating to check the rest.
public function myFunction(MyClass $object){
foreach(func_get_args() as $object){
if(!($object instanceof MyClass)){
// throw exception or something
}
$this->_objects[] = $object;
}
}
Any better solutions?
Purpose:
A container object that acts as an iterated list of the child objects, with some utility functions. calling it with a variadic constructor would be something like this:
// returned directly from include
return new MyParent(
new MyChild($params),
new MyChild($params),
new MyChild($params)
);
The other option could be an addChild method chain:
$parent = new MyParent;
return $parent
->addChild(new MyChild($params))
->addChild(new MyChild($params))
->addChild(new MyChild($params));
The children take several arguments to their constructor as well, so I'm trying to balance between legibility and processing expense.
This is now possible with PHP 5.6.x, using the ... operator (also known as splat operator in some languages):
Example:
function addDateIntervalsToDateTime( DateTime $dt, DateInterval ...$intervals )
{
foreach ( $intervals as $interval ) {
$dt->add( $interval );
}
return $dt;
}
Well I would say it depends on the number of arguments :) There is nothing like a list (all arguments 1-n as MyClass [before PHP 5.6, for PHP 5.6+ see Variadic functions]), it's more that you need to write each argument (as in your question) and it's allowed to send more but only the ones defined will be checked.
However you can define more and make them optional. Hence why I just wrote it depends on the number of arguments:
public function myFunction(MyClass $object, MyClass $object2=null, MyClass $object3=null, MyClass $object4=null, ...){
foreach(func_get_args() as $object){
if(null === $object){
// throw exception or something
}
$this->_objects[] = $object;
}
}
With such an example, PHP would have thrown the exception already when the argument is not NULL and not MyClass (Passing NULL if given as default value, is possible to pass as well). Optional parameter should not be part of the return value of func_get_args(). I mean, if you don't pass the third argument (here named $object3) it won't appear in the array returned by func_get_args().
No idea if that is more practicable for you than your the code in the question.
If you face more such situations in your code, you can create some sort of helper to validate the input of function calls and delegate to throw the exceptions with nice error message from the helper. That will prevent duplicate code and will give more comfort in development as you can make the exception notices nicer as you write them for multiple cases.
According to your new feedback, if you want first of all the interpreter let the work of checking, a little iteration and the addMember function would do it:
class MyParent {
private $children = array();
public function __construct() {
foreach(func_get_args() as $object) $this->addChild($object);
}
public function addChild(MyChild $object) {
$this->children[] = $object;
}
}
Instantiating the object with a wrong type of object in any of the list will prevent the Collection to be instantiated.
you can then just do this:
// returned directly from include
return new MyParent(
new MyChild($params),
new MyChild($params),
new MyChild($params)
);