php: Instantiate class on-the-fly - php

Is it possible to instantiate a class from a string, without declaring another variable before ?
It's usually done writing
$className = 'myClass'
$instance = new $className();
but it could be handy to have it shorter like for example
$instance = new ${'className'}();
The purpose is to have objects created (under condition) inside a loop without use of extra vars...
Edit : $className is dynamic, it is hard coded above to explain the situation

See factory pattern.
class Foo {
static function factory($class, array $args = null) {
return new $class($args);
}
}
// class factoring; returns a new instance of requested class ($className)
Foo::factory($className);
I added optional arguments array if you want to set some class properties.
// pass some values for class constructor
Foo::factory($className, array('arg1' => 1, 'arg2' => 2, 'args3' => 3));
Furthermore, you can build "fluid" interfaces so you can "chain" methods when you use that pattern:
Foo::factory($className)->method1()->method2(array('param' => 'value'))->etc();
where method1(), method2() must return $this (the object itself) to chain multiple method calls in one line.

You could make a factory function (or class/method) that takes a class name as a parameter, and then call it with the result of your dynamic PHP code that generates the string. You might consider it a bit cleaner but it's not going to save you any memory or speed.
class foo { }
function factory($class) { return new $class(); }
foreach (...) {
$instance = factory(<some code that returns the string 'foo'>);
}

It's one extra variable, does it really make much of a difference? The answer is that unless you use eval (which comes with security issues) it isn't possible to do it any shorter than your first example.

Related

Parameter to define a type of class

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.

Static instance array in instance method

The I18n class in CakePHP provides this method to create instances:
public static function getInstance() {
static $instance = array();
if (!$instance) {
$instance[0] = new I18n();
}
return $instance[0];
}
Among other considerations (please correct me if I'm wrong), I understand it helps to use class instances from the convenience functions:
/**
* Returns a translated string if one is found; Otherwise, the submitted message.
*/
function __($singular, $args = null) {
// ...
$translated = I18n::translate($singular);
// ...
}
echo __('Hello, World!');
This looks cleaner than having to pass the instance around as argument (or, even worse, using a randomly named global variable). But I can't imagine a reason why $instance is an array rather than a plain object.
What can be the purpose of using a one-item array to store class instances?
I would suspect this to be leftovers from older PHP4/CakePHP versions where the instances were assigned by reference.
https://github.com/cakephp/cakephp/blob/1.2.0/cake/libs/i18n.php
function &getInstance() {
static $instance = array();
if (!$instance) {
$instance[0] =& new I18n();
$instance[0]->l10n =& new L10n();
}
return $instance[0];
}
$_this =& I18n::getInstance();
Assigning by reference doesn't work with static, the reference is not being remembered, but it works when assigned to an array entry.
So this was most probably just a workaround for a PHP limitation.
One possible reason for this is to keep all singleton class instances in one global - (static is a synonym of global in this case) array variable for monitoring or not messing the global/local namespace with individual variables for each singleton. If each of the static variables were with random names e.g $translated it would be more easier to overwrite and mess its value. - bug again for me, this is extremely rear possibility.
For example the I18Nn instance would be with [0] key, other class would have other key. You should check outher singleton classes how manage the static $instance array values.

Can PHP objects be constructed and their variables set in one operation?

In perl I'm used to doing
my $foo = new WhatEver( bar => 'baz' );
and now I'm trying to figure out if PHP objects can ever be constructed this way. I only see this:
my $foo = new WhatEver();
$foo->{bar} = 'baz';
is it possible to do it in one step?
You can lay out your constructor as follows:
class MyClass {
public function __construct($obj=null) {
if ($obj && $obj instanceof Traversable || is_array($obj)) {
foreach ($obj as $k => $v) {
if (property_exists($this,$k)) {
$this->{$k} = $v;
}
}
}
}
}
This has a serie of drawbacks:
This is inefficient
The variables you create will not show up on any doc software you use
This is the open door to all forms of slackery
However, it also presents the following benefits:
This can be extended pretty safely
It allows you to lazy-implement variables
It also allows you to set private variables, provided that you know their names. It is pretty good in that respect if not abused.
The parameters passed in the parentheses (which can be omitted, by the way, if there aren't any) go to the constructor method where you can do whatever you please with them. If a class is defined, for example, like this:
class WhatEver
{
public $bar;
public function __construct($bar)
{
$this -> bar = $bar;
}
}
You can then give it whatever values you need.
$foo = new WhatEver('baz');
There are a few ways to accomplish this, but each has its own drawbacks.
If your setters return an instance of the object itself, you can chain your methods.
my $foo = new WhatEver();
$foo->setBar("value")->setBar2("value2");
class WhatEver
{
public $bar;
public $bar2;
public function setBar($bar)
{
$this->bar = $bar;
return $this;
}
public function setBar2($bar2)
{
$this->bar2 = $bar2;
return $this;
}
}
However, this doesn't reduce it to one step, merely condenses every step after instantiation.
See: PHP method chaining?
You could also declare your properties in your constructor, and just pass them to be set at creation.
my $foo = new WhatEver($bar1, $bar2, $bar3);
This however has the drawback of not being overtly extensible. After a handful of parameters, it becomes unmanageable.
A more concise but less efficient way would be to pass one argument that is an associative array, and iterate over it setting each property.
The implicit assumption here is that objects have meaningful, presumably public, properties which it is up to the calling code to provide values for. This is by no means a given - a key aspect of OOP is encapsulation, so that an object's primary access is via its methods.
The "correct" mechanism for initialising an object's state is its constructor, not a series of property assignments. What arguments that constructor takes is up to the class definition.
Now, a constructor might have a long series of named parameters, so that you could write $foo = new WhatEver(1, "hello", false, null) but if you want these to act like options, then it could take a single hash - in PHP terms, an Array - as its argument.
So, to answer the question, yes, if your constructor is of the form function __construct(Array $options) and then iterates over or checks into $options. But it's up to the constructor what to do with those options; for instance passing [ 'use_safe_options' => true ] might trigger a whole set of private variables to be set to documented "safe" values.
As of PHP 5.4 (which introduced [ ... ] as an alternative to array( ... )), it only takes a few more character strokes than the Perl version:
$foo = new WhatEver( ['bar' => 'baz'] );

Is is possible to store a reference to an object method?

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

Access class constant and static method from string

I have a string containing the class name and I wish to get a constant and call a (static) method from that class.
<?php
$myclass = 'b'; // My class I wish to use
$x = new x($myclass); // Create an instance of x
$response = $x->runMethod(); // Call "runMethod" which calls my desired method
// This is my class I use to access the other classes
class x {
private $myclass = NULL;
public function __construct ( $myclass ) {
if(is_string($myclass)) {
// Assuming the input has a valid class name
$this->myclass = $myclass;
}
}
public function runMethod() {
// Get the selected constant here
print $this->myclass::CONSTANT;
// Call the selected method here
return $this->myclass::method('input string');
}
}
// These are my class(es) I want to access
abstract class a {
const CONSTANT = 'this is my constant';
public static function method ( $str ) {
return $str;
}
}
class b extends a {
const CONSTANT = 'this is my new constant';
public static function method ( $str ) {
return 'this is my method, and this is my string: '. $str;
}
}
?>
As I expected (more or less), using $variable::CONSTANT or $variable::method(); doesn't work.
Before asking what I have tried; I've tried so many things I basically forgot.
What's the best approach to do this? Thanks in advance.
To access the constant, use constant():
constant( $this->myClass.'::CONSTANT' );
Be advised: If you are working with namespaces, you need to specifically add your namespace to the string even if you call constant() from the same namespace!
For the call, you'll have to use call_user_func():
call_user_func( array( $this->myclass, 'method' ) );
However: this is all not very efficient, so you might want to take another look at your object hierarchy design. There might be a better way to achieve the desired result, using inheritance etc.
in php 7 you can use this code
echo 'my class name'::$b;
or
#Uncomment this lines if you're the input($className and $constName) is safe.
$reg = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/';
if(preg_match($reg,$className) !== 1 || preg_match($reg,$constName) !== 1)
throw new \Exception('Oh, is it an attack?');
$value = eval("return $className::$constName;");
You can achieve it by setting a temporary variable. Not the most elegant way but it works.
public function runMethod() {
// Temporary variable
$myclass = $this->myclass;
// Get the selected constant here
print $myclass::CONSTANT;
// Call the selected method here
return $myclass::method('input string');
}
I guess it's to do with the ambiguity of the ::, at least that what the error message is hinting at (PHP Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM)
Use call_user_func to call static method:
call_user_func(array($className, $methodName), $parameter);
Classes defined as abstract may not be instantiated, and any class that contains at least one abstract method must also be abstract. Methods defined as abstract simply declare the method's signature - they cannot define the implementation.
When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child; additionally, these methods must be defined with the same (or a less restricted) visibility. For example, if the abstract method is defined as protected, the function implementation must be defined as either protected or public, but not private. Furthermore the signatures of the methods must match, i.e. the type hints and the number of required arguments must be the same. This also applies to constructors as of PHP 5.4. Before 5.4 constructor signatures could differ.
Refer to http://php.net/manual/en/language.oop5.abstract.php
This might just be tangential to the subject but, while searching for my own issue I found that the accepted answer pointed me in the right direction, so I wanted to share my problem & solution in case someone else might be stuck in a similar fashion.
I was using the PDO class and was building some error options from an ini config file. I needed them in an associative array in the form: PDO::OPTION_KEY => PDO::OPTION_VALUE, but it was of course failing because I was trying to build the array with just PDO::$key => PDO::$value.
The solution (inspired from the accepted answer):
$config['options'] += [constant('PDO::'.$key) => constant('PDO::'.$option)];
where everything works if you concatenate the class name and the Scope Resolution Operator as a string with the variable and get the constant value of the resulting string through the constant function (more here).
Thank you and I hope this helps someone else!

Categories