php passing and looping an array into a class method - php

i'm some what new to php classes, so apologies if this is an obvious one.
i'm trying pass an array to a class method, but i get a warning when i try to foreach loop it
Warning: Invalid argument supplied for foreach() in....
the foreach does actually loop the array and gives me the desired result, so i'm confused about the warning, and i'm not finding any simple explanation on how to fix it/ do it the correct way. i also tried passing the array as a json_encode then decoding back to an array once inside the method, and the same warning occurs. what also confuses me, i get nothing if i print_r($this->payload) inside the method.
here's my class...
// class
// --------------------------
class Form {
public $form_action;
public $form_method;
public $form_class;
public $form_role;
public $payload;
function form() {
$output = '';
foreach ($this->payload as $key => $value) {
$output .= '<input type="'.$key.'" name="'.$key.'" ......>';
}
return $output;
}
}
here's the array...
// array
// --------------------------
$my_array = (object)array (
"form1" => (object)array (
"foo1" => "bar1",
"foo2" => "bar2"
)
);
and here's where i pass the array to the class...
// set class
// --------------------------
$set = new Form;
$set->form_action = '';
$set->form_method = 'post';
$set->form_class = 'form-signin';
$set->form_role = 'form';
$set->payload = $my_array->form1;
echo $set->form();

There is no option1 in your array (what is actually object). Anyway, why do you call it array, when its an object? So that will be $my_array->form1
And you are using the same name of your function to show the form, as the name of the class, so that will be the constructor. And there will be no value for $this->payload at the first run.
So your final code will be this:
class Form {
public $form_action;
public $form_method;
public $form_class;
public $form_role;
public $payload;
function showForm() {
$output = '';
foreach ($this->payload as $key => $value) {
$output .= '<input type="' . $key . '" name="' . $key . '" ......>';
}
return $output;
}
}
$my_array = (object) array(
"form1" => (object) array(
"foo1" => "bar1",
"foo2" => "bar2"
)
);
$set = new Form;
$set->form_action = '';
$set->form_method = 'post';
$set->form_class = 'form-signin';
$set->form_role = 'form';
$set->payload = $my_array->form1;
echo $set->showForm();

I can't tell from your warning message. But I think that if you were to look at the stack trace that the warning is being generated via the line $set = new Form;
From the php manual about constructors
For backwards compatibility, if PHP 5 cannot find a __construct()
function for a given class, and the class did not inherit one from a
parent class, it will search for the old-style constructor function,
by the name of the class. Effectively, it means that the only case
that would have compatibility issues is if the class had a method
named __construct() which was used for different semantics.
Because in your example class, your method shares the same name as the class and does not have a __construct method. The form() method is being called when you create the object at which point $this->payload is null.
You can fix this by either creating a __construct method or in your class definition initializing $this->payload to an empty array.

Related

__construct function not working as expected

I'm trying to build a __construct function for my class. This function should get All $_REQUEST values and store them in an array so they can be called later by id. The problem is that it doesn't store my variables and I don't know why.
Seeing this is my first "serious" attempt at a construct, it's most probably my stupidity. But I'd like to know what that is.
Class Regex {
private static $requests = array();
function __construct() {
foreach($_REQUEST as $key => $value) {
self::$requests[$key] = array(
'value' => $value,
'status' => false,
'errorList' => array()
);
}
}
public static function preg($key, $rules) {
var_dump(self::$requests); // for test purpose
}
}
The result of above is: array (size=0) empty.
Are you even calling the constructor? A constructor is only called when calling it either explicitly or via the new keyword).
PHP doesn't have anything like static constructors like Java has.
You have to ensure that the array is filled at the first access to preg() method:
public static function preg($key, $rules) {
if (empty(self::$requests)) {
foreach($_REQUEST as $key => $value) {
self::$requests[$key] = array(
'value' => $value,
'status' => false,
'errorList' => array()
);
}
}
var_dump(self::$requests); // for test purpose
}
the constructor of your Regex class is called upon creating a new regex object like so:
$regex = new Regex;
but you never create a Regex object so the constructor is never called, resulting in an empty $requests array.
You work with a static function. I think you don't call the construct method. The __construct function is called if you make a new instance.
$regex = new Regex;
If you call the static class for example Regex::preg the contructor is not called.
Expanding upon what bwoebi has answered, you can still get the intended results simply by adding a call to the static function preg from the constructor itself as you can see bellow:
That said, this is just adding more bloat without any real benefit.
Whilst this could come in useful in certain cases, and I only added it within this answer as a mean to display that your current structure could work with the simple addition of two simple lines, I would recommend going with #bwoebi's answer but to keep in mind that whilst the controller is initself not static, it does not in any way or form stop it from communicating with static methods.
PHP:
class Regex {
private static $requests = array();
function __construct(){
if (empty(self::$requests)) {
foreach($_REQUEST as $key => $value) {
self::$requests[$key] = array(
'value' => $value,
'status' => false,
'errorList' => array()
);
}
}
self::preg();
}
public static function preg(){
var_dump(self::$requests); // for test purpose
}
}
$var = new Regex();

PHP - Indirect modification of overloaded property

I know this question has been asked several times, but none of them have a real answer for a workaround. Maybe there's one for my specific case.
I'm building a mapper class which uses the magic method __get() to lazy load other objects. It looks something like this:
public function __get ( $index )
{
if ( isset ($this->vars[$index]) )
{
return $this->vars[$index];
}
// $index = 'role';
$obj = $this->createNewObject ( $index );
return $obj;
}
In my code I do:
$user = createObject('user');
$user->role->rolename;
This works so far. The User object doesn't have a property called 'role', so it uses the magic __get() method to create that object and it returns its property from the 'role' object.
But when i try to modify the 'rolename':
$user = createUser();
$user->role->rolename = 'Test';
Then it gives me the following error:
Notice: Indirect modification of overloaded property has no effect
Not sure if this is still some bug in PHP or if it's "expected behaviour", but in any case it doesn't work the way I want. This is really a show stopper for me... Because how on earth am I able to change the properties of the lazy loaded objects??
EDIT:
The actual problem only seems to occur when I return an array which contains multiple objects.
I've added an example piece of code which reproduces the problem:
http://codepad.org/T1iPZm9t
You should really run this in your PHP environment the really see the 'error'. But there is something really interesting going on here.
I try to change the property of an object, which gives me the notice 'cant change overloaded property'. But if I echo the property after that I see that it actually DID change the value... Really weird...
All you need to do is add "&" in front of your __get function to pass it as reference:
public function &__get ( $index )
Struggled with this one for a while.
Nice you gave me something to play around with
Run
class Sample extends Creator {
}
$a = new Sample ();
$a->role->rolename = 'test';
echo $a->role->rolename , PHP_EOL;
$a->role->rolename->am->love->php = 'w00';
echo $a->role->rolename , PHP_EOL;
echo $a->role->rolename->am->love->php , PHP_EOL;
Output
test
test
w00
Class Used
abstract class Creator {
public function __get($name) {
if (! isset ( $this->{$name} )) {
$this->{$name} = new Value ( $name, null );
}
return $this->{$name};
}
public function __set($name, $value) {
$this->{$name} = new Value ( $name, $value );
}
}
class Value extends Creator {
private $name;
private $value;
function __construct($name, $value) {
$this->name = $name;
$this->value = $value;
}
function __toString()
{
return (string) $this->value ;
}
}
Edit : New Array Support as requested
class Sample extends Creator {
}
$a = new Sample ();
$a->role = array (
"A",
"B",
"C"
);
$a->role[0]->nice = "OK" ;
print ($a->role[0]->nice . PHP_EOL);
$a->role[1]->nice->ok = array("foo","bar","die");
print ($a->role[1]->nice->ok[2] . PHP_EOL);
$a->role[2]->nice->raw = new stdClass();
$a->role[2]->nice->raw->name = "baba" ;
print ($a->role[2]->nice->raw->name. PHP_EOL);
Output
Ok die baba
Modified Class
abstract class Creator {
public function __get($name) {
if (! isset ( $this->{$name} )) {
$this->{$name} = new Value ( $name, null );
}
return $this->{$name};
}
public function __set($name, $value) {
if (is_array ( $value )) {
array_walk ( $value, function (&$item, $key) {
$item = new Value ( $key, $item );
} );
}
$this->{$name} = $value;
}
}
class Value {
private $name ;
function __construct($name, $value) {
$this->{$name} = $value;
$this->name = $value ;
}
public function __get($name) {
if (! isset ( $this->{$name} )) {
$this->{$name} = new Value ( $name, null );
}
if ($name == $this->name) {
return $this->value;
}
return $this->{$name};
}
public function __set($name, $value) {
if (is_array ( $value )) {
array_walk ( $value, function (&$item, $key) {
$item = new Value ( $key, $item );
} );
}
$this->{$name} = $value;
}
public function __toString() {
return (string) $this->name ;
}
}
I've had this same error, without your whole code it is difficult to pinpoint exactly how to fix it but it is caused by not having a __set function.
The way that I have gotten around it in the past is I have done things like this:
$user = createUser();
$role = $user->role;
$role->rolename = 'Test';
now if you do this:
echo $user->role->rolename;
you should see 'Test'
Though I am very late in this discussion, I thought this may be useful for some one in future.
I had faced similar situation. The easiest workaround for those who doesn't mind unsetting and resetting the variable is to do so. I am pretty sure the reason why this is not working is clear from the other answers and from the php.net manual. The simplest workaround worked for me is
Assumption:
$object is the object with overloaded __get and __set from the base class, which I am not in the freedom to modify.
shippingData is the array I want to modify a field of for e.g. :- phone_number
// First store the array in a local variable.
$tempShippingData = $object->shippingData;
unset($object->shippingData);
$tempShippingData['phone_number'] = '888-666-0000' // what ever the value you want to set
$object->shippingData = $tempShippingData; // this will again call the __set and set the array variable
unset($tempShippingData);
Note: this solution is one of the quick workaround possible to solve the problem and get the variable copied. If the array is too humungous, it may be good to force rewrite the __get method to return a reference rather expensive copying of big arrays.
I was receiving this notice for doing this:
$var = reset($myClass->my_magic_property);
This fixed it:
$tmp = $myClass->my_magic_property;
$var = reset($tmp);
I agree with VinnyD that what you need to do is add "&" in front of your __get function, as to make it to return the needed result as a reference:
public function &__get ( $propertyname )
But be aware of two things:
1) You should also do
return &$something;
or you might still be returning a value and not a reference...
2) Remember that in any case that __get returns a reference this also means that the corresponding __set will NEVER be called; this is because php resolves this by using the reference returned by __get, which is called instead!
So:
$var = $object->NonExistentArrayProperty;
means __get is called and, since __get has &__get and return &$something, $var is now, as intended, a reference to the overloaded property...
$object->NonExistentArrayProperty = array();
works as expected and __set is called as expected...
But:
$object->NonExistentArrayProperty[] = $value;
or
$object->NonExistentArrayProperty["index"] = $value;
works as expected in the sense that the element will be correctly added or modified in the overloaded array property, BUT __set WILL NOT BE CALLED: __get will be called instead!
These two calls would NOT work if not using &__get and return &$something, but while they do work in this way, they NEVER call __set, but always call __get.
This is why I decided to return a reference
return &$something;
when $something is an array(), or when the overloaded property has no special setter method, and instead return a value
return $something;
when $something is NOT an array or has a special setter function.
In any case, this was quite tricky to understand properly for me! :)
This is occurring due to how PHP treats overloaded properties in that they are not modifiable or passed by reference.
See the manual for more information regarding overloading.
To work around this problem you can either use a __set function or create a createObject method.
Below is a __get and __set that provides a workaround to a similar situation to yours, you can simply modify the __set to suite your needs.
Note the __get never actually returns a variable. and rather once you have set a variable in your object it no longer is overloaded.
/**
* Get a variable in the event.
*
* #param mixed $key Variable name.
*
* #return mixed|null
*/
public function __get($key)
{
throw new \LogicException(sprintf(
"Call to undefined event property %s",
$key
));
}
/**
* Set a variable in the event.
*
* #param string $key Name of variable
*
* #param mixed $value Value to variable
*
* #return boolean True
*/
public function __set($key, $value)
{
if (stripos($key, '_') === 0 && isset($this->$key)) {
throw new \LogicException(sprintf(
"%s is a read-only event property",
$key
));
}
$this->$key = $value;
return true;
}
Which will allow for:
$object = new obj();
$object->a = array();
$object->a[] = "b";
$object->v = new obj();
$object->v->a = "b";
I have run into the same problem as w00, but I didn't had the freedom to rewrite the base functionality of the component in which this problem (E_NOTICE) occured. I've been able to fix the issue using an ArrayObject in stead of the basic type array(). This will return an object, which will defaulty be returned by reference.

php magic method to catch array_push

I am looking for a way to intercept the action in array_push, because when it will be retrieve it each value of the array has another info like:
class ClassName {
var $test = array();
function __set($attr, $value) {
$this->$attr = 'My extra value'.$value;
}
function index(){
array_push($this->test, "some val");
array_push($this->test, "some other val");
print_r($this->test);
}
}
$o = new ClassName();
$o->index();
And expected to get something like:
Array
(
[0] => My extra value some val
[1] => My extra value some other val
)
But i get:
Array
(
[0] => some val
[1] => some other val
)
Thanks to all
Instead of using an array, you can use a class that implements the ArrayAccess interface. This way you have full control over what occurs when you append to the array.
http://php.net/manual/en/class.arrayaccess.php
The drawback is that not all array functions will work on the object (ie sorting etc), but you can push like so:
$object[] = 'new value';
The alternative is to simply make a wrapper function for adding to the array.
public function addToArray($key, $value) {
if ($key === null) {
$this->test[] = 'My extra value ' . $value;
} else {
$this->test[$key] = 'My extra value ' . $value;
}
}
I don't totally understand what you're asking, but are you trying to do this:
$this->test['key'] = "some val";
That will allow you to setup your own output nicely. Because array_push() will throw on another nested level.
Update: Maybe something along these lines?
function push($prefix)
{
$refl = new ReflectionClass($this);
$prop = $refl->getProperties();
foreach($prop as $p) {
$this->{$p} = $prefix . $this->{$p};
}
}
To achieve what you're looking for, I suggest you create yourself a function that prefixes any value independent to it's use:
function prefix($value) {
return 'My extra value '.$value;
}
You can then make use of that function inside the index() function:
function index()
{
$test = array("some val", "some other val");
foreach($test as $value)
{
$this->test[] = $this->prefix($value);
}
print_r($this->test);
}
From the PHP manual:
__set() is run when writing data to inaccessible properties.
__get() is utilized for reading data from inaccessible properties.
This is only called on reading/writing inaccessible properties. Your property however is public, which means it is accessible. Changing the access modifier to protected solves the issue.
Try this:
class ClassName {
private $test = array();
public function __set($attr, $value) {
$this->test[$attr] = $value;
}
public function __get($attr) {
return $this->test[$attr];
}
public function index(){
array_push($this->test, "some val");
array_push($this->test, "some other val");
return $this->test;
}
}
$o = new ClassName();
$o->setData = 'My extra value';
print_r($o->index());
The PHP manual says the following about magic methods:
__set() is run when writing data to inaccessible properties. (Emphasis mine)
Because the test property is inside the method and the function that is accessing it is inside the method, the function will see the variable directly and will not use the magic setter.
Additionally, even if you try to use array_push on the magic setter outside the class itself, that still will not work. You will get the error array_push() expects parameter 1 to be array, object given.
If you want to support array_push, you should write your own push($item) method:
function push($item) {
$this->test[] = 'My extra value' . $item
}
or:
function push() {
$items = func_get_args();
// Using array_map is one of the many ways to do this.
// You could also do it with a simpler `foreach` but I like this way.
$prepend = function($item) {
return 'My extra value' . $item;
};
$items = array_map($prepend, $items);
array_push($this->test, $items);
}
if you want to support pushing multiple items at once.

How can I make different methods in my form-builder class?

If I create a new PHP class e.g. to simplify form building (yes I know there are some out there) but I am also trying to learn about classes so pls. be patient - thanks ...
OK I create a new class in the usual way
class newform { class details in here }
add a construct function
public function __construct() { function in here }
I can then call that class again in the usual way
$newform = new newform();
so far so good .... (for me anyhow).
Now I can add some args to the function like so
public function __construct($args) { function in here }
and inside the function "go through" the args - which in my case is an array so written like this
$newform = new newform($args = array('arg1'=>'arg1 val','arg2'=>'arg2 val'));
I can do all that but how do I "add further functions" What I mean here is at the moment I have to declare a new class for every input: i.e.
$newform = new newform($args = array('arg1'=>'arg1 val','arg2'=>'arg2 val'));
$newform->textarea;
$newform = new newform($args = array('arg1'=>'arg1 val','arg2'=>'arg2 val'));
$newform->textinput;
That seems "very" long winded to me and therefore wrong.
How do you do something like this (syntax I know is wromg) where textarea and textinput are created in the class a bit like this (but without the args) $this->textarea = '<textarea></textarea>';
$newform = new newform();
$newform->textarea($args);
$newform->textinput($args);
$newform->textarea($args);
What I mean is what additional function/s do you put into the class to allow you to firstly declare the class ($newform = new newform();) then pass $args to items within the class so you can do "something" like above?
Hope I am explaining myself.
If the arguments in the parameter array are related to individual form elements, move the parameter to a new function instead of the passing it to the class constructor. Like so:
class newform {
public function __construct() { }
public function make_textarea(array $args) {
/* do stuff here */
return $formatted_textarea; // a string like '<textarea></textarea>'
}
public function make_input(array $args) {
/* do stuff here */
return $formatted_input; // a string like '<input />'
}
}
Then in your template:
$form = new newForm;
echo $form->make_textarea(array('arg1' => 'val1', 'arg2' => 'val2'));
echo $form->make_input(array('arg1' => 'val3', 'arg2' => 'val4'));
Note: I'm not doing ($args = array('arg1'=> when calling the method. Assigning the array to a variable is not necessary.
Note: Notice the array type hinting: make_textarea(array $args). That's only there to make sure an array is passed to the method. If anything else is passed to the method--a string for example--a Fatal Error will be thrown.
Update - How to use a private method
class Example {
public function do_something(array $args) {
$result = $this->private_method($args);
return $result;
}
private function private_method(array $args) {
/* do stuff here */
return $formatted_args;
}
}
It isn't long-winded to declare functions for each type of tag you want to generate. There are a finite number of tags, and rather than relying on dynamically intercepting function calls via __call you're better off simply defining the methods.
Move most of the internal implementation for each type of tag can be moved to a private method for generating generic HTML tags. Not all form elements share any internal implementation though; tags like <input type="password" /> and <input type="text" /> are obvious candidates for a shared implementation, while <select> elements will require special handling.
The following should give you an idea. When you build your own, don't forget to escape htmlspecialchars where appropriate:
class Form_helper {
// pass boolean false for $contents to build a self-closing "<input />"-style tag
private function html_tag($name, $contents, array $attributes = array() {
$tag = "<$name";
foreach ($attributes as $key => $value) {
$tag .= " $key=\"$value\"";
}
if ($contents === false) {
// self-closing
$tag .= " />";
} else {
$tag .= ">$contents</$name>";
}
return $tag;
}
public function textarea($contents, array $attributes = array()) {
return $this->html_tag('textarea', $contents, $attributes);
}
public function input(array $attributes = array()) {
return $this->html_tag('input', false, $attributes);
}
public function select(array $options) {
// options contains "value"=>"contents" mappings, for production
// option tags in the form <option value="value">contents</option>
$option_tags = '';
foreach ($options as $value => $content) {
$option_tags .= $this->html_tag('option', $content, array('value' => $value));
}
return $this->html_tag('select', $option_tags);
}
}
First of all, you don't have to do this:
$newform = new newform($args = array('arg1'=>'arg1 val','arg2'=>'arg2 val'));
This will suffice:
$newform = new newform(array('arg1'=>'arg1 val','arg2'=>'arg2 val'));
That is, if you want to pass an array as the first argument. Normally, you would do something like this instead:
class newform {
public function __construct($arg1, $arg2) {
// method body here
}
}
$form = new newform('arg1 val', 'arg2 val');
Now, you must keep in mind that a constructor (__construct) is just like another method. So you can do this:
class newform {
public function __construct() {
// method body here
}
public function textarea($name) {
echo '<textarea name="'.$name.'"></textarea>';
}
public funciton textinput($name) {
echo '<input type="text" name="'.$name.'"/>';
}
}
$form = new newform;
$form->textarea('foo');
$form->textinput('bar');
Outputs:
<textarea name="foo"></textarea>
<input type="text" name="bar"/>
I'm not sure what you mean, but my guts tell me that what you need are so-called magic methods:
magic methods / overloading / manual
Best
Raffael

How do I get the type of constructor parameter via reflection?

I'm using type hinting on my constructor parameter list like so:
public function __construct(FooRepository $repository)
Is there a way to use the PHP Reflection API to get the hinted type? In other words, I want a reflection function that I can call to somehow get back the string "FooRepository". I've tried getting the constructor via reflection and then getting the parameters if the constructor, but I don't see anything that will give me the string of the hinted type.
Try this out.
class Foo {
public function __construct(Bar $test) {
}
}
class Bar {
public function __construct() {
}
}
$reflection = new ReflectionClass('Foo');
$params = $reflection->getConstructor()->getParameters();
foreach ($params AS $param) {
echo $param->getClass()->name . '<br>';
}
Check out PHP 5.4
They are planning to get out PHP 5.4 this year, which will have the reflection method (current in the dev builds) of parameter->getHint()
However, until 5.4 goes GA, I am using ReflectionClass::getDocComment()
For example, you can specify it in #param.
// Adapted from meager's example
class Bar {}
class Foo {
/**
* #param MyType $value
* #param array $value2
*/
function __construct(Bar $value, array $value2) {
}
}
// Regex
function getHint( $docComment, $varName ) {
$matches = array();
$count = preg_match_all('/#param[\t\s]*(?P<type>[^\t\s]*)[\t\s]*\$(?P<name>[^\t\s]*)/sim', $docComment, $matches);
if( $count>0 ) {
foreach( $matches['name'] as $n=>$name ) {
if( $name == $varName ) {
return $matches['type'][$n];
}
}
}
return null;
}
$reflection = new ReflectionClass('Foo');
$constructor= $reflection->getConstructor();
$docComment = $constructor->getDocComment();
$params = $constructor->getParameters();
foreach ($params AS $param) {
$name = $param->getName();
echo $name ." is ";
//echo $param->getHint()."\n"; // in PHP 5.4
echo getHint($docComment, $name)."\n"; // work around
}
Output:
value is MyType
value2 is array
Are you trying to get the hinted type, or the actual type? I can't see why you could want to get the hinted type, as you know it is 'FooRepository' or PHP would have raised an error.
You can get the actual type via get_class and you can also find out if a object inherits from a given class with ReflectionClass::isSubclassOf.

Categories