refactoring old php with reflection - php

so I am refactoring some old code written by someone else. I've come across this:
$r = new \ReflectionClass($className);
$args = (is_subclass_of($className, 'ClassA')) ? [$arg1, $arg2] : [$arg2];
$classInstance = $r->newInstanceArgs($args);
my question is, why would the person do this? Doesn't this do the same?
$args = (is_subclass_of($className, 'ClassA')) ? [$arg1, $arg2] : [$arg2];
$classInstance = new $className($args);
The code is a few years old - the reason could have something to do with old PHP version features. I'm just looking for reasons and confirmation that the second way is "better". Thanks.

the answer is because of the "dynamic argument list." So the second version would have to look like this to be correct:
if (is_subclass_of($className, 'ClassA')) {
$classInstance = new $className($arg1, $arg2);
} else {
$classInstance = new $className($arg2);
}
passing an array to either constructor doesn't work:
class ClassA
{
public $arg1;
public $arg2;
public function __construct($arg1, $arg2)
{
$this->arg1 = $arg1;
$this->arg2 = $arg2;
}
}
$className = 'ClassA';
$args = (is_subclass_of($className, 'ClassA')) ? [1, 2] : [2];
$classInstance = new $className($args);
var_dump($classInstance);
// object(ClassA)[1]
// public 'arg1' =>
// array (size=1)
// 0 => int 2
// public 'arg2' => null

Yes, for this purpose ReflectionClass is unnecessary.
You need to be sure that $r is not used in other part of the code before to remove it.
FYI:
is_subclass_of is available since PHP 4
ReflectionClass::isSubclassOf is available since PHP 5
You should check if instanceof match with your requirements, I think that it's a good solution
http://php.net/instanceof
($a instanceof MyClass)

Related

Generating a php object, two levels deep

I'm new to php - objects and arrays, especially. Coming from a JavaScript world, I'm having a modicum of trouble understanding the right way to construct objects, that may easily be iterated.
I'd like to create an object (or array - although I suspect an object would be more suitable) with the following structure:
$client_body:
$cst:
$title: 'Unique string'
$copy: function_result()
$ser:
$title: 'Unique string'
$copy: function_result()
$imp
$title: 'Unique string'
$copy: function_result()
...
I've been trying with variations on the following, but with numerous errors:
$client_body = new stdClass();
$client_body->cst->title = 'Client case study';
$client_body->cst->copy = get_field('client_cst');
$client_body->ser->title = 'Our service';
$client_body->ser->copy = get_field('client_ser');
...
And it seems that, using this approach, I'd have to use a new stdClass invocation with each new top-level addition, which seems a little verbose.
Could someone point me in the right direction?
You can just typecast an array to an object:
$client_body = (object)array(
"cst" => (object)array(
"title" => "Unique string",
"copy" => function_result()
)
);
You can try this object class more OOP:
<?php
class ClientBody{
protected $cst;
protected $ser;
protected $imp;
public function __construct($cst = '', $ser ='', $imp = '')
{
$this->cst = $cst;
$this->ser = $ser;
$this->imp = $imp;
}
public function getCst()
{
return $this->cst;
}
public function getSer()
{
return $this->ser;
}
public function getImp()
{
return $this->imp;
}
public function setCst($value)
{
$this->cst = $value;
}
public function setSer($value)
{
$this->ser = $value;
}
public function setImp($value)
{
$this->imp = $value;
}
}
$myObject = new ClientBody('toto', 'titi', 'tata');
echo $myObject->getCst(); // output 'toto'
echo $myObject->getSer(); // output 'titi'
echo $myObject->getImp(); // output 'tata'
Or you could use json_decode($client_body, TRUE);

Use Reflection to find the class that a method belongs to

I'm working in Magento, but this is more of a general PHP question. The situation is that within Magento, there are classes that extend classes that extend classes that extend classes. I want to be able to quickly find which class actually contains the definition for a method and/or if that method is actually magic.
So for instance, if I'm 10 levels deep into classes extending others, and 4 of those 10 classes have a method called getHtml, I want to be able to find out which one of those methods is actually being called when I call $this->getHtml(). I would also like to be able to tell if getHtml is actually a magic method.
How can I do this with the PHP Reflection Class, or any other programmatic means?
(untested code below — if you find any bugs I'd appreciate an update in the comments)
The best you'll be able to do with the Reflection API is find the classes in the hierarchy where the method is not defined. The ReflectionClass's hasMethod feature will take parent classes into account — a better name for it might be aClassInTheHierarchyHasThisMethod. Consider this (quick top-my-head) inline function
$getClassesWithMethod = function($classOrObject, $method, $return=false) use(&$getClassesWithMethod)
{
$return = $return ? $return : new ArrayObject;
$r = new ReflectionClass($classOrObject);
if($r->hasMethod($method))
{
$return['has ' . $method . ' method'][] = $r->getName();
}
else
{
$return['no '. $method . ' method'][] = $r->getName();
}
$parent = $r->getParentClass();
if($parent)
{
$getClassesWithMethod($parent->getName(), $method, $return);
}
return $return;
};
$product = Mage::getModel('catalog/product');
$classesWithMethod = $getClassesWithMethod($product, 'load');
var_dump((array)$classesWithMethod);
Run the above, and you'll get
array (size=2)
'has load method' =>
array (size=4)
0 => string 'Mage_Catalog_Model_Product' (length=26)
1 => string 'Mage_Catalog_Model_Abstract' (length=27)
2 => string 'Mage_Core_Model_Abstract' (length=24)
'no load method' =>
array (size=1)
0 => string 'Varien_Object' (length=13)
So you know Varien_Object doesn't have the method load defined, which means it shows up first in Mage_Core_Model_Abstract. However, you won't know if there's also a definition in Mage_Catalog_Model_Abstract or Mage_Catalog_Model_Product. The Reflection API won't get you this.
What can get you this using the token_get_all method. This method can break a PHP file down into it's component PHP/Zend tokens. Once you have that, you can write a small parser in PHP that identifies method/function definitions in a specific class file. You can use this to recursively check the hierarchy. Again, an inline function.
$getClassesWithConcreteDefinition = function($classOrObject,$method,$return=false) use(&$getClassesWithConcreteDefinition)
{
$return = $return ? $return : new ArrayObject;
$r = new ReflectionClass($classOrObject);
$tokens = token_get_all(file_get_contents($r->getFilename()));
$is_function_context = false;
foreach($tokens as $token)
{
if(!is_array($token)){continue;}
$token['name'] = token_name($token[0]);
if($token['name'] == 'T_WHITESPACE'){ continue; }
if($token['name'] == 'T_FUNCTION')
{
$is_function_context = true;
continue;
}
if($is_function_context)
{
if($token[1] == $method)
{
$return[] = $r->getName();
}
$is_function_context = false;
continue;
}
}
$parent = $r->getParentClass();
if($parent)
{
$getClassesWithConcreteDefinition($parent->getName(),$method,$return);
}
return $return;
};
$product = Mage::getModel('catalog/product');
$hasActualDefinition = $getClassesWithConcreteDefinition($product, 'setData');
var_dump((array)$hasActualDefinition);
Here we're checking for the setData method. The above will return
array (size=2)
0 => string 'Mage_Catalog_Model_Abstract' (length=27)
1 => string 'Varien_Object' (length=13)
Because setData is defined in both the Mage_Catalog_Model_Abstract class and the Varien_Object class. You should be able to modify these functions to fit your own needs. Good luck!
4 of those 10 classes have a method called getHtml, I want to be able to find out which one of those methods is actually being called when I call $this->getHtml().
The method you're probably looking for is ReflectionClass::getMethods() which tells method-names and their resp. classname already.
If you can't find the concrete method-name, you need to look for __class.
The following is an example function that does this for public methods:
function traverseParentsForMethod($objOrClassname, $methodName) {
$refl = new ReflectionClass($objOrClassname);
$methods = $refl->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
if ($method->getName() === $methodName) {
return $method;
}
}
if ($methodName === '__call') {
return null;
}
return traverseParentsForMethod($objOrClassname, '__call');
}
Demo: https://eval.in/132292

Dynamically call Class with variable number of parameters in the constructor

I know that it is possible to call a function with a variable number of parameters with call_user_func_array() found here -> http://php.net/manual/en/function.call-user-func-array.php . What I want to do is nearly identical, but instead of a function, I want to call a PHP class with a variable number of parameters in it's constructor.
It would work something like the below, but I won't know the number of parameters, so I won't know how to instantiate the class.
<?php
//The class name will be pulled dynamically from another source
$myClass = '\Some\Dynamically\Generated\Class';
//The parameters will also be pulled from another source, for simplicity I
//have used two parameters. There could be 0, 1, 2, N, ... parameters
$myParameters = array ('dynamicparam1', 'dynamicparam2');
//The instantiated class needs to be called with 0, 1, 2, N, ... parameters
//not just two parameters.
$myClassInstance = new $myClass($myParameters[0], $myParameters[1]);
You can do the following using ReflectionClass
$myClass = '\Some\Dynamically\Generated\a';
$myParameters = array ('dynamicparam1', 'dynamicparam2');
$reflection = new \ReflectionClass($myClass);
$myClassInstance = $reflection->newInstanceArgs($myParameters);
PHP manual: http://www.php.net/manual/en/reflectionclass.newinstanceargs.php
Edit:
In php 5.6 you can achieve this with Argument unpacking.
$myClass = '\Some\Dynamically\Generated\a';
$myParameters = ['dynamicparam1', 'dynamicparam2'];
$myClassInstance = new $myClass(...$myParameters);
I implement this approach a lot when function args are > 2, rather then end up with an Christmas list of arguments which must be in a specific order, I simply pass in an associative array. By passing in an associative array, I can check for necessary and optional args and handle missing values as needed. Something like:
class MyClass
{
protected $requiredArg1;
protected $optionalArg1;
public function __construct(array $options = array())
{
// Check for a necessary arg
if (!isset($options['requiredArg1'])) {
throw new Exception('Missing requiredArg1');
}
// Now I can just localize
$requiredArg1 = $options['requiredArg1'];
$optionalArg1 = (isset($options['optionalArg1'])) ? $options['optionalArg1'] : null;
// Now that you have localized args, do what you want
$this->requiredArg1 = $requiredArg1;
$this->optionalArg1 = $optionalArg1;
}
}
// Example call
$class = 'MyClass';
$array = array('requiredArg1' => 'Foo!', 'optionalArg1' => 'Bar!');
$instance = new $class($array);
var_dump($instance->getRequiredArg1());
var_dump($instance->getOptionalArg1());
I highly recommend using an associative array, however it is possible to use a 0-index array. You will have to be extremely careful when constructing the array and account for indices that have meaning, otherwise you will pass in an array with offset args and wreck havoc with your function.
You can do that using func_get_args().
class my_class {
function __construct( $first = NULL ) {
$params = func_get_args();
if( is_array( $first ) )
$params = $first;
// the $params array will contain the
// arguments passed to the child function
foreach( $params as $p )
echo "Param: $p\n";
}
}
function my_function() {
$instance = new my_class( func_get_args() );
}
echo "you can still create my_class instances like normal:";
$instance = new my_class( "one", "two", "three" );
echo "\n\n\n";
echo "but also through my_function:";
my_function( "one", "two", "three" );
Basically, you simply pass the result of func_get_args to the constructor of your class, and let it decide whether it is being called with an array of arguments from that function, or whether it is being called normally.
This code outputs
you can still create my_class instances like normal:
Param: one
Param: two
Param: three
but also through my_function:
Param: one
Param: two
Param: three
Hope that helps.
I've found here
Is there a call_user_func() equivalent to create a new class instance?
the example:
function createInstance($className, array $arguments = array())
{
if(class_exists($className)) {
return call_user_func_array(array(
new ReflectionClass($className), 'newInstance'),
$arguments);
}
return false;
}
But can somebody tell me if there is an example for classes with protected constructors?

Passing named parameters to a php function through call_user_func_array

When trying to call a function in a child class with an arbitrary set of parameters, I'm having the following problem:
class Base{
function callDerived($method,$params){
call_user_func_array(array($this,$method),$params);
}
}
class Derived extends Base{
function test($foo,$bar){
print "foo=$foo, bar=$bar\n";
}
}
$d = new Derived();
$d->callDerived('test',array('bar'=>'2','foo'=>1));
Outputs:
foo=2, bar=1
Which... is not exactly what I wanted - is there a way to achieve this beyond re-composing the array with the index order of func_get_args? And yes, of course, I could simply pass the whole array and deal with it in the function... but that's not what I want to do.
Thanks
No. PHP does not support named parameters. Only the order of parameters is taken into account. You could probably take the code itself apart using the ReflectionClass to inspect the function parameter names, but in the end you'd need to use this to reorder the array anyway.
The stock PHP class ReflectionMethod is your friend.
Example:
class MyClass {
function myFunc($param1, $param2, $param3='myDefault') {
print "test";
}
}
$refm = new ReflectionMethod('MyClass', 'myFunc');
foreach ($refm->getParameters() as $p)
print "$p\n";
And the result:
Parameter #0 [ <required> $param1 ]
Parameter #1 [ <required> $param2 ]
Parameter #2 [ <optional> $param3 = 'myDefault' ]
At this point you know the names of the parameters of the target function. With this information you can modify your method 'callDerived', and you can re-order the array to call_user_func_array according to the parameter names.
Good news, I had the same concern (I was looking for named arguments in PHP, like Python does), and found this useful tool : https://github.com/PHP-DI/Invoker
This uses the reflection API to feed a callable with some arguments from an array and also use optional arguments defaults for other parameters that are not defined in the array.
$invoker = new Invoker\Invoker;
$result = $invoker->call(array($object, 'method'), array(
"strName" => "Lorem",
"strValue" => "ipsum",
"readOnly" => true,
"size" => 55,
));
Have fun
UPDATE: PHP 8 Now supports named parameters. And it works with call_user_func_array if you pass an associative array. So you can simply do this:
<?php
function myFunc($foo, $bar) {
echo "foo=$foo, bar=$bar\n";
}
call_user_func_array('myFunc', ['bar' => 2, 'foo' => 1]);
// Outputs: foo=1, bar=2
In your code, you'll be happy to know that you don't have to change a thing. Just upgrade to PHP 8 and it'll work as you expected
You can simply pass an array and extract:
function add($arr){
extract($arr, EXTR_REFS);
return $one+$two;
}
$one = 1;
$two = 2;
echo add(compact('one', 'two')); // 3
This will extract as references, so there is close to no overhead.
I use a bitmask instead of boolean parameters:
// Ingredients
define ('TOMATO', 0b0000001);
define ('CHEESE', 0b0000010);
define ('OREGANO', 0b0000100);
define ('MUSHROOMS', 0b0001000);
define ('SALAMI', 0b0010000);
define ('PEPERONI', 0b0100000);
define ('ONIONS', 0b1000000);
function pizza ($ingredients) {
$serving = 'Pizza with';
$serving .= ($ingredients&TOMATO)?' Tomato':'';
$serving .= ($ingredients&CHEESE)?' Cheese':'';
$serving .= ($ingredients&OREGANO)?' Oregano':'';
$serving .= ($ingredients&MUSHROOMS)?' Mushrooms':'';
$serving .= ($ingredients&SALAMI)?' Salami':'';
$serving .= ($ingredients&ONIONS)?' Onions':'';
return trim($serving)."\n" ;
}
// Now order your pizzas!
echo pizza(TOMATO | CHEESE | SALAMI);
echo pizza(ONIONS | TOMATO | MUSHROOMS | CHEESE); // "Params" are not positional
For those who still might stumble on the question (like I did), here is my approach:
since PHP 5.6 you can use ... as mentioned here:
In this case you could use something like this:
class Base{
function callDerived($method,...$params){
call_user_func_array(array($this,$method),$params);
}
}
class Derived extends Base{
function test(...$params){
foreach ($params as $arr) {
extract($arr);
}
print "foo=$foo, bar=$bar\n";
}
}
$d = new Derived();
$d->callDerived('test',array('bar'=>'2'),array('foo'=>1));
//print: foo=1, bar=2
There is a way to do it and is using arrays (the most easy way):
class Test{
public $a = false;
private $b = false;
public $c = false;
public $d = false;
public $e = false;
public function _factory(){
$args = func_get_args();
$args = $args[0];
$this->a = array_key_exists("a",$args) ? $args["a"] : 0;
$this->b = array_key_exists("b",$args) ? $args["b"] : 0;
$this->c = array_key_exists("c",$args) ? $args["c"] : 0;
$this->d = array_key_exists("d",$args) ? $args["d"] : 0;
$this->e = array_key_exists("e",$args) ? $args["e"] : 0;
}
public function show(){
var_dump($this);
}
}
$test = new Test();
$args["c"]=999;
$test->_factory($args);
$test->show();
a full explanation can be found in my blog:
http://www.tbogard.com/2013/03/07/passing-named-arguments-to-a-function-in-php/

PHP - How to get object from array when array is returned by a function?

how can I get a object from an array when this array is returned by a function?
class Item {
private $contents = array('id' => 1);
public function getContents() {
return $contents;
}
}
$i = new Item();
$id = $i->getContents()['id']; // This is not valid?
//I know this is possible, but I was looking for a 1 line method..
$contents = $i->getContents();
$id = $contents['id'];
You should use the 2-line version. Unless you have a compelling reason to squash your code down, there's no reason not to have this intermediate value.
However, you could try something like
$id = array_pop($i->getContents())
Keep it at two lines - if you have to access the array again, you'll have it there. Otherwise you'll be calling your function again, which will end up being uglier anyway.
I know this is an old question, but my one line soulution for this would be:
PHP >= 5.4
Your soulution should work with PHP >= 5.4
$id = $i->getContents()['id'];
PHP < 5.4:
class Item
{
private $arrContents = array('id' => 1);
public function getContents()
{
return $this->arrContents;
}
public function getContent($strKey)
{
if (false === array_key_exists($strKey, $this->arrContents)) {
return null; // maybe throw an exception?
}
return $this->arrContents[$strKey];
}
}
$objItem = new Item();
$intId = $objItem->getContent('id');
Just write a method to get the value by key.
Best regards.

Categories