&__get() issues, again. Major frustration is afoot - php

Alrighty, I'm getting quite frustrated, namely because I thought I had this issue solved, or had accomplished this successfully before.
Quick preliminary:
PHP 5.3.6.
Error reporting cranked to 11. (-1 actually; future safe, all errors/notices)
I have a class, it aggregates request parameters. For giggles here is a stripped down version:
class My_Request{
private $_data = array();
public function __construct(Array $params, Array $session){
$this->_data['params'] = $params;
$this->_data['session'] = $session;
}
public function &__get($key){
// arrg!
}
}
Anyways, the reason for arrg! is, no matter what I try, I always get an error whenever the $key doesn't exist. I've tried:
// doesn't work
$null = null;
if(isset($this->_data[$key])){ return $this->_data[$key]; }
return $null;
// doesn't work
return $this->_data[$key];
I've been told ternary operators cannot result in a reference, ergo, that of course doesn't work, but we know that from the if condition attempt anyways. What happens, for example:
// params will have foo => bar, and session hello => world
$myRequest = new My_Request(array('foo' => 'bar'), array('hello' => 'world'));
// throws an error - Undefined index: baz
echo $myRequest->params['baz'];
I'm losing my mind here; perhaps I hallucinated a scenario where I achieved this. Is it not possible to (without throwing a notice) successfully do this?
Clarification: Things I've tried
The aforementioned:
// no check, no anything, just try returning : fails
public function &__get($key){
return $this->_data[$key];
}
// null variable to pass back by reference : fails
public function &__get($key){
$null = null;
if(isset($this->_data[$key])){
return $this->_data[$key];
}
return $null;
}
Other attempts:
// can't work - can't return null by reference nor via ternary : fails
public function &__get($key){
return isset($this->_data[$key])
? $this->_data[$key]
: null;
}

echo $myRequest->params['baz'];
The isset check in your __get function will look up "params" from $this->_data and return the array. The notice you get is from outside the class and about a key "baz" in the returned array - which in your example was never actually defined.

I realize this question is stale, but I just stumbled on it via Google while looking for the answer (which I have since found).
class My_Request{
private $_data = array();
public function __construct(Array $params, Array $session){
$this->_data['params'] = $params;
$this->_data['session'] = $session;
}
public function &__get($key){
if (array_key_exists($key, $this->_data)) {
return &$this->_data[$key]; // Note the reference operator
} else {
$value = null; // First assign null to a variable
return $value; // Then return a reference to the variable
}
}
}
$this->_data[$key] is an operation that returns a value, so returning the value will result in an error because it's not a reference. To make it return a reference instead, you have to use the reference: &$this->_data[$key].

Haven't tried this because I avoid __get and __set, but maybe this would work for you:
public function __get($key){
if(!isset($this->_data[$key]))
return false;
return $this->_data[$key];
}
Totally untested, but it looks like it could maybe do the job.

Related

Default value if variable not sets

I sometimes have variables that might not be set and I would like to use a default parameter instead. Like here:
if ($p == "a") doSomething();
If $p is not defined PHP throws Notice: Undefined variable. To avoid this I often I used this construct in such a case:
$p = (isset($p) ? $p : "");
But that is ugly if you have to use it a lot. So I wrote a function for it:
function getIfSet(&$value, $default = '')
{
return isset($value) ? $value : $default;
}
// Example
if (getIfSet($p) == "a") doSomething();
I wonder if there is a PHP function for this or how you solve this.
Just a little improvement, prefer passing null value to $default, passing empty string can be confusing, cause correct value can be empty string.
function getIfSet(&$value, $default = null)
{
return isset($value) ? $value : $default;
}
$p = getIfSet($p);
isset() is about as clean as it gets. Although I must admit that I'm not too fond of defaulting to an empty string, simply because a variable could be an empty string, yet still "be set". I think that a default of bool false or null would be truer to the behavior of isset:
function getIfSet(&$value, $default = false)
{
return isset($value) ? $value : $default;
}
$p = getIfSet($p);
if($p !== false){
//insert or whatever
}
else{
header('Location: error.php');
exit;
}
Depending on what kind of values you're checking (maybe REQUEST data?), consider using classes. They are fun and they could be available anywhere.
Assuming you're checking POST data (if you don't, well, take this as an idea), create a class that checks this array:
class Post
{
public function __get($index)
{
if (isset($_POST[$index]))
return $_POST[$index];
else
return null;
}
}
As simple as that. You know that __get() will trigger when you try to access a non-existant property. In this case, if the property (actually, the index in the $_POST array) doesn't exist, null will be returned and no errors are generated.
Now you can do:
$params = new Post();
$foo = $params->name ?: ''; // of course this doesn't make much sense.
if (!$params->password) ...
// instead of
if (isset($_POST['password'])) ...
// you'll still have to use isset for cases like:
if (isset($_POST['user']['password']) ...
if (isset($params->user['password'])) ...
// but still looks neater I'd say
A problem you'll find soon is that $params isn't a super global variable, while $_POST are. How to solve this? Create it in the constructor of your controller class and use Dependency Injection for all other objects your are using.
I tried to make renocor's answer more clean and OOP when I came up with this solution:
class NiceArray implements ArrayAccess {
protected $array;
public function __construct(&$array) {
$this->array =& $array;
}
public function offsetExists($offset) {
return true;
}
public function offsetGet($offset) {
if (isset($this->array[$offset]))
{
return $this->array[$offset];
}
else
{
return null;
}
}
public function offsetSet($offset, $value) {
$this->array[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->array[$offset]);
}
}
Usage:
$get = new NiceArray($_GET);
if ($get['p'] == "a") doSomething();
I know the class is kind of big but this way you still have an array and you can easily use it for every array you want. You do not need to change any code you may had before. You can still access and change the data. It will even change the original array.

How can i copy an object?

I'm really stuck with references in php. In my programm i've got something similiar to this:
Programm
class Test
{
private $_property;
function __construct($property)
{
$this->_property = $property;
}
public function setProperty($property)
{
$this->_property = $property;
}
public function getProperty()
{
return $this->_property;
}
}
function doSmth(Test $var)
{
$newVar = new Test('test');
//I need to do something here...
}
$var = new Test('original');
doSmth($var);
var_dump($var);
Question
What should i do to copy all contents of $newVar variable to my $var variable so that i will be able to see it after using var_dump() function that is outside of function doSmth(). And i can't use getters and setters in my programm because i've got a lot of them and it will be a lot of code. Is it possible to solve this problem with my limitations?
UPDATE: I can't return value in my function doSmth() and i also tried __clone but nothing works. Can someone show me how can i do it with __clone()?
It is possible to create a copy of object by using a clone keyword. It will help you to create full replica of source object. OR, what I think also might be useful for you, you can try to pass Test object by reference:
function doSmth(Test &$var)
{
// Here you can do something with your var
}
$newVar only exists in the context of the function.
What you want to do is to create a return value like this:
function doSmth(Test $var)
{
$newVar = new Test('test');
//I need to do something here...
return $newVar;
}
and call the function like this instead:
$myNewShinyVar=doSmth($var);
This way, your function returns the object of the class Test as a a return value.
var_dump($myNewShinyVar);
Edit: If you cannot return a value, you can pass by reference instead:
function doSmth(&$var)
{
$var = new Test('test');
}
This will modify the variable itself that is passed as a parameter to it (rather than passing a copy of it)
doSmth($var);
var_dump($var);
If I read your question a number of times I think is this what you are after:
function doSmth(Test $var)
{
$newvar = clone $var;
// do some stuff on a copy of $var, leaving $var intact
}
$var = new Test('original');
doSmth($var);
var_dump($var);

Chained PHP controls

I'm making a form validation class and it works like this currently.
$validator->setVar($_POST['Username'])
->standardFilter(array('XSS', 'SQL_INJECTION'))
->customRegex()
->replace('This', 'With this')
->getResult();
While it works perfectly when chained like this, I can't archieve the following result.
$validator->setVar($_POST['Username'])
->isValidEmail()
->isValidPhoneNumber()
->isSet()
->isNull()
->getResult()
For example, script returns the following values
->isValidEmail() (true)
->isValidPhoneNumber() (true)
->isSet() (false)
Basically, I'm going to make an array, fill it with true/false depending on the result of each function, and I'll look for a specific value in array (a false). If it exists, the class will return false regardless of the rest of the chains. (or I can just override variables, not important here.)
However, I want $validator to stop chaining once it gets a false from a function. Let's say it received a false from isSet(). It shouldn't execute isNull() and getResult() since we already have a failed check.
How can I archieve this in PHP?
TL;DR:
var_dump($validator->setVar('Test message')->isInteger()->setTrue());
//false //true
Output: false, because once isInteger() failed, rest of the chain isn't executed.
How can I archieve this in PHP?
Nothing like good source code to learn from. I would suggest exploring the Zend Framework's Validation classes. It provides the same chaining functionality you describe.
...More source code check isValid() specifically.
Try something like this
class FooBar
{
private $SomethingWrong = false;
function Bar()
{
if( $this->SomethingWrong )
throw new Exception('SomeThing is wrong');
return $this;
}
function Foo()
{
return $this
}
}
$foobar = new FooBar();
$foobar->Bar()
->Foo();
The Foo() part will not be executed, because of the exception in the Bar().
Of course, there are some variations. If you do not want a exception, but a silent non-execute, you could try this:
class FooBar
{
private $SomethingWrong = false;
function Bar()
{
$this->SomethingWrong = true;
return $this;
}
function Foo()
{
if( !$this->SomethingWrong ) {
// do my stuff
}
return $this
}
}
The only way to do this, in any language, is to throw an exception. You can't return the validator object (which is necessary for chaining) and also return true or false, all while having the chaining work. That said, I am not advocating the use of exceptions in this manner. I am in complete agreement with vascowhite's comments below.
Rather than have it stop in the middle of the chain, why not consider the isSet, isNull, etc. methods as instructions to tell the validator what to check. Then have a validate method called at the end of the chain. The validate method can perform the validation based on the validator state (as set by the other methods). And that validate method can also return a true or a false, or a custom state object, with the result of the validation.
Instead of return a value, you can throw a custom exception, which abort the code execution.
Add an try-catch block to the code, handle your exception and everything works fine.
EDIT:
What you also can do is a little bit magic and not really to be recommed. But nice to know, this is possible in php, so better use Exceptions
class PassThroughValidator extends ...
{
private $val;
public function __construct($result)
{
$this->val = $result;
}
public function __call($name, $arguments)
{
return $this;
}
public function getResult()
{
return $this->val;
}
}
class EmailValidator extends ...
{
function isMail()
{
if (...) {
// do something here
return $this;
}
// set Result to false or something similar
return new PassThroughValidator($this->getResult());
}
}
Considering that the value returned in each step of the chain is an object, you can not have one of the chained methods return true/false. it must always return an object instance. So I guess what you would need to do is add some property on the object to indicate that validations should not be done, and if the property is set, just ignore the validation attempt and return the object as is.
So perhaps something like this in simplified form, showing only one such validation:
class validator {
protected $ignore_validations = false;
protected $value = null;
protected $is_null;
public function isNull () {
if(true === $this->ignore_validations) {
return $this;
} else if(is_null($this->value)) {
$this->is_null = true;
$this->ignore_validations = true;
return $this;
} else {
$this->is_null = false;
return $this;
}
}
}

PHP object method doesn't behave as I expect

I can't quite understand why the output of this code is '1'.
My guess is that php is not behaving like most other OO languages that I'm used to, in that the arrays that php uses must not be objects. Changing the array that is returned by the class does not change the array within the class. How would I get the class to return an array which I can edit (and has the same address as the one within the class)?
<?php
class Test
{
public $arr;
public function __construct()
{
$this->arr = array();
}
public function addToArr($i)
{
$this->arr[] = $i;
}
public function getArr()
{
return $this->arr;
}
}
$t = new Test();
$data = 5;
$t->addToArr($data);
$tobj_arr = $t->getArr();
unset($tobj_arr[0]);
$tobj_arr_fresh = $t->getArr();
echo count($tobj_arr_fresh);
?>
EDIT: I expected the output to be 0
You have to return the array by reference. That way, php returns a reference to the array, in stead of a copy.
<?php
class Test
{
public $arr;
public function __construct()
{
$this->arr = array();
}
public function addToArr($i)
{
$this->arr[] = $i;
}
public function & getArr() //Returning by reference here
{
return $this->arr;
}
}
$t = new Test();
$data = 5;
$t->addToArr($data);
$tobj_arr = &$t->getArr(); //Reference binding here
unset($tobj_arr[0]);
$tobj_arr_fresh = $t->getArr();
echo count($tobj_arr_fresh);
?>
This returns 0.
From the returning references subpage:
Unlike parameter passing, here you have to use & in both places - to
indicate that you want to return by reference, not a copy, and to
indicate that reference binding, rather than usual assignment, should
be done
Note that although this gets the job done, question is if it is a good practice. By changing class members outside of the class itself, it can become very difficult to track the application.
Because array are passed by "copy on write" by default, getArr() should return by reference:
public function &getArr()
{
return $this->arr;
}
[snip]
$tobj_arr = &$t->getArr();
For arrays that are object, use ArrayObject. Extending ArrayObject is probably better in your case.
When you unset($tobj_arr[0]); you are passing the return value of the function call, and not the actual property of the object.
When you call the function again, you get a fresh copy of the object's property which has yet to be modified since you added 5 to it.
Since the property itself is public, try changing:
unset($tobj_arr[0]);
To: unset($t->arr[0]);
And see if that gives you the result you are looking for.
You are getting "1" because you are asking PHP how many elements are in the array by using count. Remove count and use print_r($tobj_arr_fresh)

Magic Method __set() on a Instantiated Object

Ok i have a problem, sorry if i cant explaint it clear but the code speaks for its self.
i have a class which generates objects from a given class name;
Say we say the class is Modules:
public function name($name)
{
$this->includeModule($name);
try
{
$module = new ReflectionClass($name);
$instance = $module->isInstantiable() ? $module->newInstance() : "Err";
$this->addDelegate($instance);
}
catch(Exception $e)
{
Modules::Name("Logger")->log($e->getMessage());
}
return $this;
}
The AddDelegate Method:
protected function addDelegate($delegate)
{
$this->aDelegates[] = $delegate;
}
The __call Method
public function __call($methodName, $parameters)
{
$delegated = false;
foreach ($this->aDelegates as $delegate)
{
if(class_exists(get_class($delegate)))
{
if(method_exists($delegate,$methodName))
{
$method = new ReflectionMethod(get_class($delegate), $methodName);
$function = array($delegate, $methodName);
return call_user_func_array($function, $parameters);
}
}
}
The __get Method
public function __get($property)
{
foreach($this->aDelegates as $delegate)
{
if ($delegate->$property !== false)
{
return $delegate->$property;
}
}
}
All this works fine expect the function __set
public function __set($property,$value)
{
//print_r($this->aDelegates);
foreach($this->aDelegates as $k=>$delegate)
{
//print_r($k);
//print_r($delegate);
if (property_exists($delegate, $property))
{
$delegate->$property = $value;
}
}
//$this->addDelegate($delegate);
print_r($this->aDelegates);
}
class tester
{
public function __set($name,$value)
{
self::$module->name(self::$name)->__set($name,$value);
}
}
Module::test("logger")->log("test"); // this logs, it works
echo Module::test("logger")->path; //prints /home/bla/test/ this is also correct
But i cant set any value to class log like this
Module::tester("logger")->path ="/home/bla/test/log/";
The path property of class logger is public so its not a problem of protected or private property access.
How can i solve this issue? I hope i could explain my problem clear.
EDIT:
A simple demonstration
Modules::Name("XML_Helper")->xmlVersion ="Hello"; // default is 333
$a = Modules::Name("XML_Helper")->xmlVersion; // now $a should contain "Hello"
echo $a; // prints 333
What i need is
Modules::Name("XML_Helper")->xmlVersion ="Hello"; // default is 333
$a = Modules::Name("XML_Helper")->xmlVersion; // now $a should contain "Hello"
echo $a; // prints Hello
I realise you already said that path is public, but it's still worth mentioning: If you're using PHP 5.3.0+, note this quirk of property_exists():
5.3.0 | This function checks the existence of a property independent of
accessibility
In other words, if you check if (property_exists($delegate, $property)), you have no guarantee you have access to $delegate->$property for writing (or reading, for that matter, but you are trying to write).
As for actual troubleshooting: You could try checking if your if (property_exists($delegate, $property)) statement actually executes. If it doesn't, check the case of $property.
Sidenote: It's fairly hard to read the code you posted up, which makes it a bit of a pain to troubleshoot. Could you edit your post and indent it properly?
The path property of class logger is public so its not a problem of
protected or private property access.
That's your problem. From the docs:
__set() is run when writing data to inaccessible properties.
That suggests that __set() is not called for public properties.

Categories