Passing an array to class contructor as params list - php

I want to create a class object and pass its parameters as an array.
For example:
$array = array (
'param1' => 123,
'param1' => 456,
'param2' => 789
);
that must be transformed into an array list of settings and passed to the constructor:
$a = new A();

With the call_user_func_array http://php.net/call_user_func_array
<?php
class A {
public function __construct ( ) {
print_r ( func_get_args ( ) ) ;
}
}
$A = new A();
$params = array ( 'param' => 'value' , 'p1' => 'val' ) ;
call_user_func_array ( array ( $A , '__construct' ) , $params ) ;

You can use reflection
<?php
class A {
public function __construct ( ) {
print_r ( func_get_args ( ) ) ;
}
}
$reflection = new ReflectionClass('A');
$reflection->newInstanceArgs(array( 'a' , 'b' )) ;

<?php
class A
{
public function __construct( $var=array() ) { print_r( $var ); }
}
$a=new A( array( 'foo','bar','baz' ) );
?>

Related

How to get variable name and value in AST

I'm using PHP-PArser to find the AST of PHP program. For example:
code
<?php
use PhpParser\Error;
use PhpParser\NodeDumper;
use PhpParser\ParserFactory;
$code = <<<'CODE'
<?php
$variable = $_POST['first'];
$new = $nonexist;
CODE;
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
try {
$ast = $parser->parse($code);
} catch (Error $error) {
echo "Parse error: {$error->getMessage()}\n";
return;
}
$dumper = new NodeDumper;
echo $dumper->dump($ast) . "\n";
The AST result of the above example as following:
array( 0: Stmt_Expression( expr: Expr_Assign( var: Expr_Variable( name: variable ) expr: Expr_ArrayDimFetch( var: Expr_Variable( name: _POST_first_symbol ) dim: Scalar_String( value: first ) ) ) ) 1: Stmt_Expression( expr: Expr_Assign( var: Expr_Variable( name: new ) expr: Expr_Variable( name: nonexist ) ) ) )
What I'm trying to find is the variable = _POST AND new = nonexist
I used leavenode function to reach _POST and variable. my code to find _POSTand variable as following:
public function leaveNode(Node $node)
{
$collect_to_print= array();
if ($node instanceof ArrayDimFetch
&& $node->var instanceof Variable
&& $node->var->name === '_POST')
{
$variableName = (string) $node->var->name;
$collect_to_print[$node->dim->value] = $node->var->name; // will store the variables in array in a way to print them all later such as variable => _POST , how to get the name `variable` in this case
return $node;
}
else
if ($node instanceof Variable
&& !($node->var->name === '_POST' ))
{
$collect_to_print[$node->name] = 'Empty' ;
}
}
My results until now show every variable in separate line as following:
variable =>
first => _POST // This _POST should be the value of variable (above)
new => Empty
nonexist => Empty
However, I expect the result to be:
variable => _POST
new => Empty
nonexist => Empty
any help please
This is a lot more complicated than other questions you've asked, but it has been interesting to learn about how to write it.
I've put comments through the code, but basically it analyses the code and looks for assignments (instances of PhpParser\Node\Expr\Assign nodes). It then splits it into left and right parts and recursively extracts any variables in either parts.
The code allows for nested variables on either side of the expression, I've changed the example code to provide some broader examples.
Comments in code (assumes some knowledge of how the parser works with nodes etc.)...
$traverser = new NodeTraverser;
class ExtractVars extends NodeVisitorAbstract {
private $prettyPrinter = null;
private $variables = [];
private $expressions = [];
public function __construct() {
$this->prettyPrinter = new PhpParser\PrettyPrinter\Standard;
}
public function leaveNode(Node $node) {
if ( $node instanceof PhpParser\Node\Expr\Assign ) {
$assignVars = $this->extractVarRefs ( $node->var );
// Get string of what assigned to actually is
$assign = $this->prettyPrinter->prettyPrintExpr($node->var);
// Store the variables associated with the left hand side
$this->expressions[$assign]["to"] = $assignVars;
// Store variables from right
$this->expressions[$assign][] = $this->extractVarRefs ( $node->expr );
}
}
private function extractVarRefs ( Node $node ) : array {
$variableList = [];
// If it's a variable, store the name
if ( $node instanceof PhpParser\Node\Expr\Variable ) {
$variable = $this->prettyPrinter->prettyPrintExpr($node);
$this->variables[] = $variable;
$variableList[] = $variable;
}
// Look for any further variables in the node
foreach ( $node->getSubNodeNames() as $newNodeName ) {
$newNode = $node->$newNodeName;
if ( $newNode instanceof Node && $newNode->getSubNodeNames()) {
// Recursive call to extract variables
$toAdd = $this->extractVarRefs ( $newNode );
// Add new list to current list
$variableList = array_merge($variableList, $toAdd);
}
}
return $variableList;
}
public function getVariables() : array {
return array_unique($this->variables);
}
public function getExpressions() : array {
return $this->expressions;
}
}
$varExtract = new ExtractVars();
$traverser->addVisitor ($varExtract);
$traverser->traverse($ast);
print_r($varExtract->getVariables());
print_r($varExtract->getExpressions());
Which gives the list of variables as...
Array
(
[0] => $_POST
[1] => $b
[3] => $new
[4] => $nonexist
)
And the list of expressions as
Array
(
[$_POST[$b]] => Array
(
[to] => Array
(
[0] => $_POST
[1] => $b
)
[0] => Array
(
[0] => $_POST
)
)
[$new] => Array
(
[to] => Array
(
[0] => $new
)
[0] => Array
(
[0] => $nonexist
)
[1] => Array
(
[0] => $_POST
[1] => $b
)
)
)
note that the [to] element of the array contains any variables involved on the left of the =.

Using instanceof to check and modify input

I am using instanceof to check class instances and want to modify input data if class already has instance with same data. below example demonstrates my problem in detail where i have two unique array inputs and third one is duplicate array of second where instanceof supposed to work and modify input.
/**
* Foo Class
*/
class Foo {
public $bar = array();
public function __construct() {}
public function add( $bar ) {
if ( $bar['ID'] instanceof Baz ) { // inctanceof not working as i am expecting. supposed to modify duplicate occurrence
//if bar['ID'] is already instance of Baz then we are trying to modify bar ID before pass it so Baz.
$bar['ID'] = $bar['ID'] . rand();
$this->bar[ $bar['ID'] ] = new Baz( $bar );
}
else {
$this->bar[ $bar['ID'] ] = new Baz( $bar );
}
}
}
Class Baz
/**
* Class Baz
*/
class Baz {
public $ID;
public function __construct( $bar ) {
$this->ID = $bar['ID'];
}
}
Instance
$foo = new Foo();
$bar = array( 'ID' => 'bar1' );
$foo->add( $bar );
$bar2 = array( 'ID' => 'bar2' );
$foo->add( $bar2 );
$bar3 = array( 'ID' => 'bar2' ); //duplicate ID
$foo->add( $bar3 );
Print
print_r( $foo );
Output
Foo Object
(
[bar] => Array
(
[bar1] => Baz Object
(
[ID] => bar1
)
[bar2] => Baz Object
(
[ID] => bar2
)
)
)
Expected Output
Foo Object
(
[bar] => Array
(
[bar1] => Baz Object
(
[ID] => bar1
)
[bar2] => Baz Object
(
[ID] => bar2
)
[bar2{random number}] => Baz Object
(
[ID] => bar2{random number}
)
)
)
what i am doing wrong in here? kindly guide me and alternate solution is also applicable.
You Foo class should look like:
class Foo {
public $bar = array();
public function __construct() {}
public function add( $bar ) {
if (isset($this->bar[ $bar['ID'] ]) && $this->bar[ $bar['ID'] ] instanceof Baz ) { // inctanceof not working as i am expecting. supposed to modify duplicate occurrence
//if bar['ID'] is already instance of Baz then we are trying to modify bar ID before pass it so Baz.
$bar['ID'] = $bar['ID'] . rand();
$this->bar[$bar['ID']]= new Baz( $bar );
}
else {
$this->bar[ $bar['ID'] ] = new Baz( $bar );
}
}
}

php get method arguments default values

I want to get list of methods inside a class as well as their arguments and default values. how can I do that? below is the code that I used:
$class = new ReflectionClass($className);
$methods = [];
foreach($class->getMethods() as $method){
if($method->class == $className && $method->name != '__construct' ){
$obj = [];
$obj['controller'] = $className;
$obj['action'] = $method->name;
$obj['params'] = array_map(function($value){return $value->name;}, $method->getParameters());
$methods[] = $obj;
}
}
The sample result of above code is like:
Array(
[0] => Array
(
[controller] => Controller,
[action] => function,
[params] => Array
(
[0] => offset,
[1] => limit
)
)
)
How can I get function arguments default values?
In your array_map function for the parameters, you can insert a check whether the parameter has a default value using ->isDefaultValueAvailable() and if so - list it using ->getDefaultValue(). See the example below based on your code and change it according to your needs.
Instead of
$obj['params'] = array_map(
function($value){return $value->name;},
$method->getParameters()
);
Use
$obj['params'] = array_map(
function($value){
return $value->name.
($value->isDefaultValueAvailable() ? '='.$value->getDefaultValue() : '');
},
$method->getParameters()
);

What is the right way to create nested classes in PHP?

I need to create a nested structure class of a multidimensional array, this is my array:
Array
(
[days] => Array
(
[0] => Array
(
[rows] => Array
(
[0] => Array
(
[activity_id] => 1
[name] => Activity 2
[city] => London
[info] => fsdsdshgsfd
)
[1] => Array
(
[activity_id] => 3
[name] => Activity 1
[city] => London
[info] => fsdhgsfd
)
)
)
[1] => Array
(
[rows] => Array
(
[0] => Array
(
[activity_id] => 3
[name] => Activity 1
[city] => London
[info] => fsdhgsfd
)
...
)
)
)
...
)
)
I have been trying to rewrite my code to make it class-driven, but I am struggling with that, what is the right way to build a class structure Itinerary->Days->Rows to replace an array? I tried something like this, I am not sure if it makes sense, I don't really understand the way how it has to be done:
class Itinerary
{
private $days = array();
public static function addDay($day) {
$this->$days[] = new ItineraryDay($day);
}
}
class ItineraryDay implements Countable
{
private $rows = array();
public static function addRow($row) {
$this->$rows[] = new ItineraryRow($row);
}
public function count()
{
return count($this->rows);
}
}
class ItineraryRow implements Countable
{
private $name;
private $city;
...
function __get($key)
{
...
}
function __set($key, $value)
{
...
}
public function count()
{
return count($this->rows);
}
}
$itinerary1 = new Itinerary();
$day1 = new ItineraryDay();
$itinerary1->addDay($day1);
$row1 = new ItineraryRow();
$day1->addRow($row1);
Can someone guide me?
It really depends what you ultimately want to do with said structure, but for a general idea I typically do something like this:
class Itinerary implements Countable
{
private $days;
public function __construct( array $days = array() )
{
$this->setDays( $days );
}
public function addDay( ItineraryDay $day )
{
$this->days[] = $day;
}
public function setDays( array $days )
{
$this->days = array();
foreach( $days as $day )
{
$this->addDay( $day );
}
}
public function count()
{
return count( $this->days );
}
}
class ItineraryDay implements Countable
{
private $rows;
public function __construct( array $rows = array() )
{
$this->setRows( $rows );
}
public function addRow( ItineraryRow $row )
{
$this->rows[] = $row;
}
public function setRows( array $rows )
{
$this->rows = array();
foreach( $rows as $row )
{
$this->addRow( $row );
}
}
public function count()
{
return count( $this->rows );
}
}
class ItineraryRow
{
private $id;
private $name;
private $city;
private $info;
public function __construct( $id, $name, $city, $info )
{
$this->id = $id;
$this->name = $name;
$this->city = $city;
$this->info = $info;
}
/* ... */
}
Then using it with the structure of your current array of data:
$days = array();
foreach( $originalData[ 'days' ] as $days )
{
$rows = array();
foreach( $days[ 'rows' ] as $row )
{
$rows[] = new ItineraryRow( $row[ 'activity_id' ], $row[ 'name' ], $row[ 'city' ], $row[ 'info' ] );
}
$days[] = new ItineraryDay( $rows );
}
$itinerary = new Itinerary( $days );
Or:
$itinerary = new Itinerary;
foreach( $originalData[ 'days' ] as $days )
{
$day = new ItineraryDay;
foreach( $days[ 'rows' ] as $row )
{
$row = new ItineraryRow( $row[ 'activity_id' ], $row[ 'name' ], $row[ 'city' ], $row[ 'info' ] )
$day->addRow( $row );
}
$itinerary->addDay( $day );
}
So, you can either pass "child" objects to the constructor (the method that constructs a new object), or add them with methods after construction. If you want the objects to be immutable, meaning you don't want to allow the objects to accept any more rows / days after construction, just make the addDay, setDays, addRow and setRows methods protected or private thereby only allowing passing "child" object through the constructors.
Be aware that, as PeeHaa already mentioned, you don't want static methods, because they operate class wide, not on individual instances of classes (objects). As a matter of fact, you cannot even use static methods the way you intended, because $this is only available in object context, not in class wide context.
But, to be honest, the question is a little bit to vague to be answered properly. We'd have to have a little more details about how you are going to construct the objects, and how you are going to use them later on.

Extended addslashes function in PHP

Hi can somebody help me with building an extended addslashes function, which will work with mixed combination of objects and arrays. For example i have this Object:
$object = new stdClass;
$object2 = new stdClass;
$object2->one = "st'r2";
$object3 = new stdClass;
$object3->one = "st'r3";
$object->one = "s'tr";
$object->two = array($object2);
$object->obj = $object3;
And i would like to get this object back escaped and with the same structure.
I have started some experiments and i get something like this:
function addslashes_extended($arr_r){
if(is_array($arr_r)){
foreach ($arr_r as $key => $val){
is_array($val) ? addslashes_extended($val):$arr_r[$key]=addslashes($val);
}
unset($val);
}else if(is_object($arr_r)){
$objectProperties = get_object_vars($arr_r);
foreach($objectProperties as $key => $value){
is_object($value) ? addslashes_extended($value):$arr_r->{$key}=addslashes($value);
}
}
return $arr_r;
}
But this is not going to work, i have to work with passing by reference i think, but i have no clue how, other solutions would be nice to have too, thanks in advance!
Try this (using array_walk):
error_reporting(E_ALL ^ E_STRICT);
ini_set('display_errors', 'on');
$data = array(
"fo'o",
'bar' => "foo'bar",
'foobar' => array(
1, 2, 'someObj' => json_decode('{"prop1": "a", "prop2": "b\'c"}')
)
);
class Util
{
public static function addslashes_extended(&$mixed) {
if (is_array($mixed) || is_object($mixed)) {
array_walk($mixed, 'Util::addslashes_extended');
}
elseif (is_string($mixed)) {
$mixed = addslashes($mixed);
}
}
}
Util::addslashes_extended($data);
print_r($data);
Output ( http://codepad.org/nUUYKWrn ):
Array
(
[0] => fo\'o
[bar] => foo\'bar
[foobar] => Array
(
[0] => 1
[1] => 2
[someObj] => stdClass Object
(
[prop1] => a
[prop2] => b\'c
)
)
)
To pass by reference, you use & before the variable name, quick example:
function inc(&$var) {
$var++;
}
$x = 5;
inc($x);
echo $x; //Prints: 6

Categories