Instantiating a new PHP class with one or many arguments - php

I have this fetch function:
public static function fetch($class, $key)
{
try
{
$obj = new $class($key);
}
catch(Exception $e)
{
return false;
}
return $obj;
}
It creates a new instance by calling that class's constructor and passing in the key. Now, how would I make it so I can pass in an array of arguments in $key, and have it like:
$obj = new $class($key[0], $key[1]...);
So that it works for one or more keys?
Hopefully that was clear enough.
Using PHP 5

This is an interesting question. If it wasn't a constructor function you were trying to give dynamic arguments to, then normally you could use call_user_func_array(). However, since the new operator is involved, there doesn't seem to be an elegant way to do this.
Reflection seems to be the consensus from what I could find. The following snippet is taken from the user comments on call_user_func_array(), and illustrates the usage quite nicely:
<?php
// arguments you wish to pass to constructor of new object
$args = array('a', 'b');
// class name of new object
$className = 'myCommand';
// make a reflection object
$reflectionObj = new ReflectionClass($className);
// use Reflection to create a new instance, using the $args
$command = $reflectionObj->newInstanceArgs($args);
// this is the same as: new myCommand('a', 'b');
?>
To shorten it up for your case, you can use:
$reflectionObject = new ReflectionClass($class);
$obj = $reflectionObject->newInstanceArgs($key);

Use reflection:
$classReflection = new ReflectionClass($class);
$obj = $classReflection->newInstanceArgs($key);

My library solves this this:
// Returns a new instance of a `$classNameOrObj`.
function fuNew($classNameOrObj, $constructionParams = array()) {
$class = new ReflectionClass($classNameOrObj);
if (empty($constructionParams)) { return $class->newInstance(); }
return $class->newInstanceArgs($constructionParams); }
The empty() test is required because newInstanceArgs() will complain if you give it an empty array, stupidly.

What does the constructor of the class look like? Does it accept an arbitrary number of arguments? It might be better to accept an array of keys instead of a list of key arguments.
call_user_func_array could probably do what you want:
$obj = new $object_class();
call_user_func_array(array($obj, '__construct'), $args);
Note that this calls the constructor twice, which could have negative side effects.

Related

Dynamically passing multiple typehinted objects into the constructor of a class in PHP 7

Say I'm typehinting a series of values to an interface in the constructor of a class:
<?php
use Interfaces\Item;
class MyClass
{
public function __construct(Item ...$items)
{
// Do stuff
}
}
I can pass these items in manually easily enough:
$myclass = new MyClass($item1, $item2);
But I'm struggling to get it working more dynamically - the following doesn't work because it expects to receive multiple instances of Item rather than an array, so it raises a TypeError:
$items = [
$item1,
$item2
];
$myclass = new MyClass($items);
I can't think of a way of dynamically building the items I want to pass through when constructing the new class without changing it to expect an array, and I'd rather not do that because typehinting will obviously catch any objects passed through that shouldn't be. Can anyone see how I might achieve this?
The splat operator (...) works two ways - you can use it in the function definition as you already have, but you can also use it to unpack an array of items into function arguments.
Try:
$myclass = new MyClass(...$items);
See https://eval.in/927133 for a full example
You can use array to creating new class:
<?php
use Interfaces\Item;
class MyClass
{
public function __construct(array $items)
{
// Do stuff
}
}
$myclass = new MyClass([$item1, $item2 ,...]); //php7.*
$myclass = new MyClass(array($item1, $item2 ,...)); //php5.*

Get PHP callable arguments as an array?

Say I have a callable stored as a variable:
$callable = function($foo = 'bar', $baz = ...) { return...; }
How would I get 'bar'?
if (is_callable($callable)) {
return func_get_args();
}
Unfortunately func_get_args() is for the current function, is it possible to get a key value pair of arguments?
You can use reflection:
$f = new ReflectionFunction($callable);
$params = $f->getParameters();
echo $params[0]->getDefaultValue();
You may want to use get_defined_vars to accomplish this, this function will return an array of all defined variables, specifically by accessing the callable index from the output array.
I came across this question because I was looking for getting the arguments for a callable which is not just the function itself. My case is
class MyClass{
public function f(){
// do some stuff
}
}
$myclass = new MyClass();
$callable = array($myclass, "f);
This is a valid callback in php. In this case the solution given by #Marek does not work.
I worked around with phps is_callable function. You can get the name of the function by using the third parameter. Then you have to check whether your callback is a function or a (class/object) method. Otherwise the Reflection-classes will mess up.
if($callable instanceof Closure){
$name = "";
is_callable($callable, false, $name);
if(strpos($name, "::") !== false){
$r = new ReflectionMethod($name);
}
else{
$r = new ReflectionFunction($name);
}
}
else{
$r = new ReflectionFunction($callable);
}
$parameters = $r->getParameters();
// ...
This also returns the correct value for ReflectionFunctionAbstract::isStatic() even though the $name always uses :: which normally indicates a static function (with some exceptions).
Note: In PHP>=7.0 this may be easier using Closures. There you can do someting like
$closure = Closure::fromCallable($callable);
$r = new ReflectionFunction($closure);
You may also cause have to distinguish between ReflectionFunction and ReflectionMethod but I can't test this because I am not using PHP>=7.0.

Creating new object instance with arguments array

I am trying to create a new instance of a class like this:
$obj = new $class;
I am doing this in a way that a common set of functions will do this for a number of classes, but now I am implementing some arguments. Now although the handler function could look like this:
function newInst($argA = null, $argB = null, $argC = null)
This would have to have all the arguments included beforehand and would have an upper limit. So, I am trying to do something like this:
function newInst() {
$obj = new $class(func_get_args());
...
}
but instead of just the first argument being applied, I would like it to apply the array as a set of arguments. I have tried
function newInst() {
$obj = new $class;
call_user_func_array(array($obj, '__construct'), func_get_args());
...
}
but that calls the __construct function twice. So, is there any way to use the arguments of a called function to create a new instance that would go through the __construct or classname function during instantiation?
If you're not opposed to using reflection: ReflectionClass::newInstanceArgs
function createInstance($class, array $arguments) {
$reflection = new ReflectionClass($class);
return $reflection->newInstanceArgs($arguments);
}
Reflection offers alot, and despite the common claim that it's "slow", it's very inoften that reflection will be a true bottleneck in your application; any possibility can be mitigated with caching anyhow.
Based on discussion, I'm just amending a hypothetical solution with count() checks and naive caching; it would still (definitely) need profiling.
function createInstance($class, array $arguments) {
static $cache = [];
switch (count($arguments)) {
case 0: return new $class();
case 1: return new $class($arguments[0]);
case 2: return new $class($arguments[0], $arguments[1]);
case 3: return new $class($arguments[0], $arguments[1], $arguments[2]);
}
if (!isset($cache[$class])) {
$cache[$class] = new ReflectionClass($class);
}
return $cache[$class]->newInstanceArgs($arguments);
}

PHP Static function creating and returning an object OR creating an object, and calling a function on it

Would this method
foreach($data as &$d)
$obj[]=ClassName::createObject($data);
or
foreach($data as &$d){
$obj[] = new ClassName;
$obj[end($obj)]->loadData($data);
}
-
class ClassName{
public static function createObject($data){
$obj = new ClassName;
//do stuff with $data
return $obj;
}
public function loadData($data){
//do stuff with $data;
}
}
The ::createObject method makes for code which is a lot less painful to read while the second method doesn't have to return huge objects.
I'm not sure how variables are handled in a language like PHP so is there a big difference in performance?
Which method would be best to use?
First looks ok. You don't need to pass $d by reference in this case. And probably you want ClassName::createObject($d). Still I'd refactor it to:
foreach($data as $d) {
$obj[]=new ClassName($d);
}
This is just ugly, not for performance reasons:
foreach($data as &$d){
$obj[] = new ClassName;
$obj[end($obj)]->loadData($data);
}
Instead you could do this:
foreach($data as &$d){
$object = new ClassName;
$object->loadData($d);
$obj[] = $object;
}
If the data is essential for the object - set it from the construct. If the data is optional, then the loadData method makes sense. But if you want to move the construct logic to a static method - this is pointless. No performance advantage.
the static method does not give any advantages. The factory method as static is functionally identical to the new keyword (which is also static). You suggest that it saves a single line of code but if you always need to call ->loadData() on the object after it's constructed then you should pass data into the constructor:
$obj = new ClassName($data);
Using a static method call in this instance does not solve any problems and simply adds complexity/bloat.

String containing function-line. How to echo in other file?

$str = "$obj = new class(); $obj->getSomeFunction();"
Is this possible? I am trying to develop a very dynamic platform to base my website off of.
Anyway to get this working? From a string by "echo $str;" it will make the object and run the function?
Instead of passing an object as a string, just create a new instance of your object. And once you've included a file.. All variables, objects, etc. exist in the file that includes that file.
Edit:
Instead of passing a class as a string, you can create classes dynamically:
<?php
class cc {
function __construct() {
echo 'hi!';
}
}
$type = 'cc';
$obj = new $type; // outputs "hi!"
?>
Alternatively you can use static classes:
<?php
class Foo {
public static function aStaticMethod() {
echo 'hi!';
}
}
Foo::aStaticMethod(); // outputs "hi!"
// or:
$classname = 'Foo';
$classname::aStaticMethod(); // outputs "hi!"
?>
I am in the process of writing my own mvc framework(as an learning project), and needed to dynamically create objects and call a method. I ended up using the reflection api in order to create a new instance of the object and then call the method. in this case i ended up passing an associative array that had two key/value pairs, the class name, and the method I wanted to call. I hope this helps.
$class = $command['class'];
$method = $command['method'];
try{
$reflectorClass = new ReflectionClass($class);
$reflectedInstance = $reflectorClass->newInstance($matches);
} catch (Exception $e) {
exceptionHandler::catchException($e);
}
try {
$reflectorMethod = new ReflectionMethod($reflectedInstance, $method);
$reflectorMethod->invoke($reflectedInstance);
} catch (Exception $e) {
exceptionHandler::catchException($e);
}
In PHP included files are executed when you call include() or require(). They follow variable scope rules and even allow you to return results as if the include was a function like so:
dynamicPlatform.php
<?php
$object = include('createObjAndDoStuff.php');
?>
createObjAndDoStuff.php
<?php
$obj = new class();
$obj->getSomeFunction();
return $obj;
?>
As #zerkms has pointed out, you probably should be using factories.
class Factory {
public static function someclass() {
include_once('./classes/someclass.php'); //Although some discourage the use of *_once() functions
$obj = new someclass();
$obj->getSomeFunction();
return $obj;
}
}
//And to get a new class instance
$object = Singleton::someclass();
Or pseudo-singletons with factories:
class SingletonFactory {
private static $someclass;
public static function someclass() {
if(!self::$someclass) {
include('./classes/someclass.php');
self::$someclass = new someclass();
self::$someclass->getSomeFunction();
}
return self::$someclass;
}
}
What you are searching for is eval but while it will do exactly what you are asking for, it's considered bad solution and can lead to messy code.
You can just include file that contains PHP code, or you can serialize meta data about the actions to be performed and then parse that data.
Depending on what you are trying to achieve, you may be interested in serializing objects in session as well as in Command Pattern (a way to encapsulate set of operations in object(s))

Categories