PHP - Make 'new' of a Class and Arguments set in a string - php

I've a Class and Arguments set in a variable like this:
$myVar = '\Api\MyClass(\DateTimeInterface::RFC3339_EXTENDED)';
I need to make a new of this variable.
I tried multiple solutions and at the end the new of the Class was solved but I cannot pass the argument.
My code is:
<?php
$myVar = '\Api\MyClass(\DateTimeInterface::RFC3339_EXTENDED)';
$className = substr($myVar, 0, strpos($myVar, "(")); // $className will be: \Api\MyClass
if (class_exists($className)) {
preg_match('/\((.*?)\)/', $myVar, $classArguments); $classArguments will be: \DateTimeInterface::RFC3339_EXTENDED
$obj = new $className($classArguments[1]); // it doesn't work
}
the problem is that $classArguments[1] is passed as string to my class. Below the difference:
// It works
$p = \DateTimeInterface::RFC3339_EXTENDED;
var_dump($p);
// and return
string(15) "Y-m-d\TH:i:s.vP"
// It doesn't work
$p = "\DateTimeInterface::RFC3339_EXTENDED";
var_dump($p);
// return
string(36) "\DateTimeInterface::RFC3339_EXTENDED"
can you help me?
Thank you.

New can only be used to create an instance. Make your constructor expecting that date format as default, so you are not required to pass it.
class MyClass {
public function __construct(string $dateFormat = DateTimeInterface::RFC3339_EXTENDED)
{
}
}
$var = 'MyClass';
$instance = new $var;
$p = "\DateTimeInterface::RFC3339_EXTENDED";
will not work, because that is now a string and not the constants value.
You also can pass something like so
class MyClass {
public function __construct(string $something = '')
{
echo $something;
}
}
$var = 'MyClass';
$text = 'Hello world';
$instance = new $var($text);
Hello world
If you need the complete string to be parsed try eval(), but not recommended.
$var = 'new MyClass("hello world");';
$instance = eval($var);
Hello world
or, but not recommended.
$var = 'MyClass("hello world")';
$instance = eval("new {$var};");
Hello world

You can use the constant function to get the value of a constant.
A simple example class that just outputs the parameter value when initialized.
class MyClass
{
function __construct($arg) {
echo "$arg\n";
}
}
new MyClass('anything'); // outputs "anything"
This is just a helper that gets the class name and argument string from a string like your $myVar.
function parseClassAndArgument(string $str): array {
preg_match('/(.*)\((.*)\)/', $str, $matches);
[, $className, $argument] = $matches;
return [$className, $argument];
}
This helper allows you to pass either a string value or a name of a constant, and guesses which one it is based on whether there is a :: in the string name. You can replace this with just the constant($argument) part if you know $myVar will always contain a constant name.
function parseArgValue(string $argument): string {
return strpos($argument, '::') ? constant($argument) : $argument;
}
This recognizes that $argument refers to a constant and outputs Y-m-d\TH:i:s.vP
$myVar = 'MyClass(\DateTimeInterface::RFC3339_EXTENDED)';
[$className, $argument] = parseClassAndArgument($myVar);
new $className(parseArgValue($argument));
This outputs the string foo as is.
$myVar = 'MyClass(foo)';
[$className, $argument] = parseClassAndArgument($myVar);
new $className(parseArgValue($argument));
This is a simplified example that only handles one parameter for the class constructor, but I hope it helps to get you along!

Related

Can I pass a type as parameter in PHP?

I'd like to pass (any type, not only PHP's primitives) Type as a function parameter. More like a C++'s template. Is it possible in PHP? imaginary code:
function foo(T a)
{
$output = new T();
//do something.
}
I tried pass the type name as string and then use settype() to the the variable to that type but settype() work only with PHP's primitives types. My goal is actually pass a class type as parameter.
If you want to instantiate something like the above, how about passing the classname as a string and then instantiating it!
function foo($obj_string)
{
$object = new $obj_string();
//do stuff with $object
}
I see that you already accepted an answer, but from the original post, it shows passing an object variable in the function. For those viewing this that need to do it that way, instead of being able to pass the name of the class as a string, you can do it this way:
class Blah
{
public $x = 123;
}
function Foo($b) {
$class = get_class($b);
$object = new $class();
var_dump($object);
}
$aa = new Blah();
Foo($aa);
I realize this is a bit old but I'll leave an answer anyway just in case it's helpful.
If I am going to pass an object as a parameter to another object, it's going to be after I have instantiated the object argument and adjusted the properties to my needs. The following is an example of how I would go about it. I'm using this on PHP 7.4.x and haven't tested on PHP 8.x yet.
Class Blah is the object that will be passed to an instance of Class Bleep after the property $x within the Class Blah object has been changed from 123 to 456.
<?php
class Blah {
public $x = 123;
function __construct() {}
function set_x($x) {
$this->x = $x;
}
function get_x() {
return $this->x;
}
}
class Bleep {
public $object;
function __construct($object) {
$this->object = $object;
}
function get_object_x() {
return $this->object->get_x();
}
}
// Example in use.
$obj_bla = new Blah();
print '<p>On instantiation of <u>$obj_bla</u> as a <strong>Blah</strong> object, $x = '.$obj_bla->get_x().'</p>';
$obj_bla->set_x(456);
print '<p>After using the method <i>set_x()</i> on <u>$obj_bla</u>, $x = '.$obj_bla->get_x().'</p>';
$obj_bleep = new Bleep($obj_bla);
print '<p>Instantiate <u>$obj_bleep</u> as a new <strong>Bleep</strong> object and pass it the instance of <u>$obj_bla</u> then use the <i>get_object_x()</i> method on <u>$obj_bleep</u> to get the value of x from the object that was passed = '.$obj_bleep->get_object_x().'</p>'
?>

Access static object property through variable name

I know its possible to access an object property/method using a variable as its name
ex.:
$propName = 'something';
$something = $object->$propName;
Is it possible to do the same w/ constants or static properties?
I've tried:
$constName = 'MY_CONST';
MyCLass::{$constName};
and
$obj::{$constName};
But nothing seems to work and I couldn't find it anywhere.
Use: Class::$$constName, this is similar to normal variable variables.
Demo:
<?php
class MyClass {
public static $var = 'A';
}
$name = 'var';
echo MyClass::$$name; // echoes 'A'
Constants can be access with the constant function:
constant('MyClass::'.$constantName)
This works for me:
<?php
class Test {
public static $nombre = "pepe";
public function __construct() {
return self;
}
}
$varName = "nombre";
echo Test::${$varName};
You can use the constant function:
constant('bar::'. $const);
constant("$obj::". $const); //note the double quote

Use variable's string into class names or other

I want to use variables inside class names.
For example, let's set a variable named $var to "index2".
Now I want to print index2 inside a class name like this:
controller_index2, but instead of doing it manually, I can just print the var name there like this:
controller_$var;
but I assume that's a syntax error.
How can I do this?
function __construct()
{
$this->_section = self::path();
new Controller_{$this->_section};
}
It's a hideous hack, but:
php > class foo { function x_1() { echo 'success'; } };
php > $x = new foo;
php > $one = 1;
php > $x->{"x_$one"}();
^^^^^^^^^^^^
success
Instead of trying to build a method name on-the-fly as a string, an array of methods may be more suitable. Then you just use your variables as the array's key.
Echo it as a string in double quotes.
echo "controller_{$var}";
Try this (based on your code in the OP):
function __construct()
{
$this->_section = self::path();
$controller_name = "Controller_{$this->_section}";
$controller = new $controller_name;
}
You can do this.... follow this syntax
function __construct()
{
$this->_section = self::path();
$classname = "Controller_".$this->_section;
$instance = new $classname();
}
Another way to create an object from a string definition is to use ReflectionClass
$classname = "Controller_".$this->_section;
$reflector = new ReflectionClass($classname);
and if your class name has no constructor arguments
$obj = $reflector->newInstance();
of if you need to pass arguments to the constructor you can use either
$obj = $reflector->newInstance($arg1, $arg2);
or if you have your arguments in an array
$obj = $reflector->newInstanceArgs($argArray);
try this:
$name = "controller_$var";
echo $this->$name;
just to add on the previous answers, if you're trying to declare new classes with variable names but all the construction parameters are the same and you are treating the instanced object all alike maybe you don't need different classes but just different instances of the same.

Using a define (or a class constant) to call a variable method?

Is it possible to :
define('DEFAULT_METHOD', 'defaultMethod');
class Foo
{
public function defaultMethod() { }
}
$foo = new Foo();
$foo->DEFAULT_METHOD();
Or do I have to :
$method = DEFAULT_METHOD;
$foo->$method();
And what about a class constant instead of a define ?
If you use a variable or constant as the method name, you have to put it into curly brackets:
$foo->{DEFAULT_METHOD}();
The same technique works for variables, including static class attributes:
class Foo {
public static $DEFAULT_METHOD = 'defaultMethod';
public function defaultMethod() { echo "Cool!\n"; }
}
$foo = new Foo();
$foo->{FOO::$DEFAULT_METHOD}();
In fact, practically any expression that results in a valid method name could be used:
$foo->{'default'.'Method'}();
You could set it to a variable first as in your example :)
Example: http://codepad.org/69W4dYP1
<?php
define('DEFAULT_METHOD', 'defaultMethod');
class Foo {
public function defaultMethod() { echo 'yay!'; }
}
$foo = new Foo();
$method = DEFAULT_METHOD;
$foo->$method();
?>

ArrayAccess in PHP -- assigning to offset by reference

First, a quote from the ole' manual on ArrayAccess::offsetSet():
This function is not called in assignments by reference and otherwise indirect changes to array dimensions overloaded with ArrayAccess (indirect in the sense they are made not by changing the dimension directly, but by changing a sub-dimension or sub-property or assigning the array dimension by reference to another variable). Instead, ArrayAccess::offsetGet() is called. The operation will only be successful if that method returns by reference, which is only possible since PHP 5.3.4.
I'm a bit confused by this. It appears that this suggests that (as of 5.3.4) one can define offsetGet() to return by reference in an implementing class, thus handling assignments by reference.
So, now a test snippet:
(Disregard the absence of validation and isset() checking)
class Test implements ArrayAccess
{
protected $data = array();
public function &offsetGet($key)
{
return $this->data[$key];
}
public function offsetSet($key, $value)
{
$this->data[$key] = $value;
}
public function offsetExists($key) { /* ... */ }
public function offsetUnset($key) { /* ... */ }
}
$test = new Test();
$test['foo'] = 'bar';
$test['foo'] = &$bar; // Fatal error: Cannot assign by reference to
// overloaded object in
var_dump($test, $bar);
Ok, so that doesn't work. Then what does this manual note refer to?
Reason
I'd like to permit assignment by reference via the array operator to an object implementing ArrayAccess, as the example snippet shows. I've investigated this before, and I didn't think it was possible, but having come back to this due to uncertainty, I (re-)discovered this mention in the manual. Now I'm just confused.
Update: As I hit Post Your Question, I realized that this is likely just referring to assignment by reference to another variable, such as $bar = &$test['foo'];. If that's the case, then apologies; though knowing how, if it is at all possible, to assign by reference to the overloaded object would be great.
Further elaboration: What it all comes down to, is I would like to have the following method aliases:
isset($obj[$key]); // $obj->has_data($key);
$value = $obj[$key]; // $obj->get_data($key);
$obj[$key] = $value; // $obj->set_data($key, $value);
$obj[$key] = &$variable; // $obj->bind_data($key, $variable);
// also, flipping the operands is a syntactic alternative
$variable = &$obj[$key]; // $obj->bind_data($key, $variable);
unset($obj[$key]); // $obj->remove_data($key);
As far as has, get, set, and remove go, they're no problem with the supported methods of ArrayAccess. The binding functionality is where I'm at a loss, and am beginning to accept that the limitations of ArrayAccess and PHP are simply prohibitive of this.
What the manual is referring to are so called "indirect modifications". Consider the following script:
$array = new ArrayObject;
$array['foo'] = array();
$array['foo']['bar'] = 'foobar';
In the above script $array['foo'] = array(); will trigger a offsetSet('foo', array()). $array['foo']['bar'] = 'foobar'; on the other hand will trigger a offsetGet('foo'). Why so? The last line will be evaluated roughly like this under the hood:
$tmp =& $array['foo'];
$tmp['bar'] = 'foobar';
So $array['foo'] is first fetched by ref and then modified. If your offsetGet returns by ref this will succeed. If not you'll get some indirect modification error.
What you want on the other hand is the exact opposite: Not fetch a value by reference, but assign it. This would theoretically require a signature of offsetSet($key, &$value), but practically this is just not possible.
By the way, references are hard to grasp. You'll get lots of non-obvious behavior and this is especially true for array item references (those have some special rules). I'd recommend you to just avoid them altogether.
This does not work with ArrayAccess, you could add yourself a public function that allows you to set a reference to an offset (sure, this looks different to using array syntax, so it's not really a sufficient answer):
class Test implements ArrayAccess{
protected $_data = array();
public function &offsetGet($key){
return $this->_data[$key];
}
...
public function offsetSetReference($key, &$value)
{
$this->_data[$key] = &$value;
}
}
$test = new Test();
$test['foo'] = $var = 'bar';
$test->offsetSetReference('bar', $var);
$var = 'foo';
echo $test['bar']; # foo
$alias = &$test['bar'];
$alias = 'hello :)';
echo $var; # hello :)
Probably such a function was forgotten when ArrayAccess was first implemented.
Edit: Pass it as "a reference assignment":
class ArrayAccessReferenceAssignment
{
private $reference;
public function __construct(&$reference)
{
$this->reference = &$reference;
}
public function &getReference()
{
$reference = &$this->reference;
return $reference;
}
}
class Test implements ArrayAccess{
...
public function offsetSet($key, $value){
if ($value instanceof ArrayAccessReferenceAssignment)
{
$this->offsetSetReference($key, $value->getReference());
}
else
{
$this->_data[$key] = $value;
}
}
Which then works flawlessly because you implemented it. That's probably more nicely interfacing than the more explicit offsetSetReference variant above:
$test = new Test();
$test['foo'] = $var = 'bar';
$test['bar'] = new ArrayAccessReferenceAssignment($var);
$var = 'foo';
echo $test['bar']; # foo
$alias = &$test['bar'];
$alias = 'hello :)';
echo $var; # hello :)

Categories