PHP ambiguous statement when setting dynamic property value of class - php

I have a class called PreferenceCollection, which I load for a user into $_SESSION['Preferences'] once they log in to my web application. This simple class uses magic methods for getting and setting values. Here are the relevant parts:
class PreferenceCollection {
private $prefs;
function __construct() {
$this->prefs=Array();
}
function __get($var) {
return $this->prefs[$var];
}
function __set($var, $value) {
$this->prefs[$var]=$value;
$this->save();
}
}
Later on in my code, I have found it necessary to set the value of a dynamically chosen property. For this example, $key='pref_some_preference' and value='something'.
$_SESSION['Preferences']->substr($key, 5)=$value;
What I expect is the equivalent of $_SESSION['Preferences']->some_preference=$value. That is, the __set magic method will be called with the first parameter of some_preference, and the second parameter, $value.
Instead, this line gives me a fatal error:
PHP Fatal error: Can't use method return value in write context
I assume that PHP is interpreting that I want to set the return value of the substr() call to something, rather than setting the property.
Is my interpretation of what is happening correct? How would I get around this problem?
For now I am working around the issue with a public set method, but am curious how I could set that property directly.

I think you want
$_SESSION['Preferences']->{substr($key, 5)} = $value;
The braces {} are required to guide the PHP parser into the correct direction ;-) Given that $key is 12345test this would call __set('test', $value).

Related

How do I get the function name outside a function in PHP?

Is this possible?
Pseudo-code:
class MyClass
{
function myFunc1()
{
...
}
function myFunc2()
{
echo GET_FUNCTION_NAME($this->myFunc1)
}
}
Wanted output:
myFunc1
In the code above the GET_FUNCTION_NAME method/function/construct/whatever would give back the textual representation of the function name given as parameter.
So the main point would be to get the name of a function as a string from outside the function.
All the code I have found deals with giving a function name via a string (eg. specifying callback methods), but none of them mentions how to get that function name without manually writing it in a string (thus duplicating code in a string and making refactoring harder than needed).
OTOH from inside the function it is easy with eg. __FUNCTION__ variable, so I'm not looking for that.
EDIT
A typical use case would be any callback method.
One example where I confronted this problem is the set_error_handler() method where it awaits a callable as first parameter. The callable can be simplified as a string. The problem is that if I specify the function name as a string, any time in the future when I will do refactoring I will have to take extra care to search for the strings as well and do special handling of them otherwise wrong references will be left there.
Not to mention the principle that any name should be defined once and any other use should refer to that one.
If the problem is that You need to specify a callback not using a string, but the function symbol itself, You can do with a help of anonymous function:
class MyClass {
function call(callable $c) {
...
}
function mycallback() {
...
}
function dosomejob() {
$this->call(function() { $this->mycallback(); })
}
}
From point of view of Your refactoring tool, there's still call to function mycallback, it's not reffered as a string.

PHP assigning $this variable to another name

I am sure I've seen once example of assigning $this to another variable, single-letter to make the code typing quicker.
class Dclass {
private $d = $this;
}
But it's not working, is throwing me an error :
Fatal error: Constant expression contains invalid operations in ..
Okay, so from the another post How to handle class variable without $this?
"$this is only available inside functions within the class." #FrankerZ
So this kind of assignment should be done inside the class constructor, or other function?

how to use constant from class as an argument definition in php function?

I have a class:
class FetchMode
{
const FetchAll = 0;
const FetchOne = 1;
const FetchRow = 2;}
and a function:
function getRecordSet(FetchMode $FetchMode){ some switch cases }
I would like to use $FetchMode as switch case criteria but receiving an error:
Catchable fatal error: Argument passed to getRecordSet() must be an instance of FetchMode, integer given
this is how I call a function:
getRecordSet(FetchMode::FetchOne);
I would like to offer a list of possible choices in calling a function.
Is it possible in php?
You've hinted PHP to expect an instance of FetchMode (just like it says in the error message), but FetchMode::FETCH* passes the constant value. You'd have to use some sort of Enum instance (which we dont have natively in PHP. (Oh well, there is SplEnum but who uses that?)) or change the method signature to exclude the typehint.
However, instead of a Switch/Case you could solve this more easily via Polymorphism and a Strategy pattern, e.g. instead of doing something like
public function getRecordSet($mode)
{
switch ($mode) {
case FetchMode::ALL:
// code to do a fetchAll
break;
case FetchMode::ONE:
// code to do a fetchOne
break;
default:
}
}
which will increase the Cylcomatic Complexity of your class and forces changes to that class and FetchMode whenever you need to add additional FetchModes, you can do:
public function getRecordSet(FetchMode $fetchModeStrategy)
{
return $fetchModeStrategy->fetch();
}
and then have an interface to protect the variation
interface FetchMode
{
public function fetch();
}
and add concrete FetchMode classes for each supported FetchMode
class FetchOne implements FetchMode
{
public function fetch()
{
// code to fetchOne
}
}
class FetchAll …
class FetchRow …
This way, you'll never have to touch the class with that getRecordSet method again because it will work for any class implementing that FetchMode inteface. So whenever you have new FetchModes, you simply add a new class, which is much more maintainable in the long run.
I don't know what you mean by
would like to offer a list of possible choices in calling a function. Is it possible in php?
but for the error part: Imagine you have a var, e.g. $foo. When you do echo $foo you don't get the name of the var but its value. This is because a var has a name and points to a value. Every access to the var basically returns the value it points to. Its the same with constants; You put the constant name in there, but basically you are pointing to your stored value. Which means getRecordSet(FetchMode::FetchOne); and getRecordSet(1); is the same.
So, getRecordSet(FetchMode $FetchMode) raises must be an instance of FetchMode because FetchMode::FetchOne points to an integer.
To fix this, you need to use getRecordSet(int $FetchMode).

Variable Reference as Function Argument

All about a Zend Application with an action helper.
I want to unset some pairs of an array by a function.
helper:
class Application_Controller_Action_Helper_TestHelper extends Zend_Contr[...]
{
public function direct(&$array)
{
if(isset($array['key']))
unset($array['key']);
}
}
controller:
$this->_helper->TestHelper($var);
How could I get it working?
Since you are now passing by reference, you can modify the variable in the method and the changes will be applied to the original variable. However, the way you have it now you are not changing the variable at all, just returning the result of the expression, like in your first example. You should have something like this instead:
class Application_Controller_Action_Helper_TestHelper extends Zend_Contr[...] {
public function direct(&$var) {
$var = $var + 1;
}
}
You must also pass it as reference:
$this->_helper->TestHelper(&$var);
UPDATE:
Ups, I had my errors turned off. You (and now me) are getting the error because...
There is no reference sign on a function call - only on function
definitions. Function definitions alone are enough to correctly pass
the argument by reference. As of PHP 5.3.0, you will get a warning
saying that "call-time pass-by-reference" is deprecated when you use &
in foo(&$a);.
ZF's HelperBroker uses return call_user_func_array(array($helper, 'direct'), $args); to call your direct() method. Check the docs, but it seems call_user_func_array passes by reference, although with several quirks.
Check out this answer.

PHP Callable Object as Object Member

I have a class Logger which, among other things has a method Log.
As Log is the most common use of the Logger instance, I have wired __invoke to call Log
Another class, "Site" contains a member "Log", an instance of Logger.
Why would this work:
$Log = $this->Log;
$Log("Message");
But not this:
$this->Log("Message");
The former fails with "PHP Fatal error: Call to undefined method Site::Log()"
Is this a limitation of the callable object implementation, or am I misunderstanding something?
Unfortunately, this is (still) a limitation of PHP, but it makes sense when you think about it, as a class can contain properties and methods that share names. For example:
<?php
class Test {
public $log;
public function __construct() {
$this->log = function() {
echo 'In Test::log property';
};
}
public function log() {
echo 'In Test::log() method';
}
}
$test = new Test;
$test->log(); // In Test::log() method
call_user_func($test->log); // In Test::log property
?>
If PHP were to allow the syntax you desire, which function would be invoked? Unfortunately, that only leaves us with call_user_func[_array]() (or copying $this->log to another variable and invoking that).
However, it would be nice if the following syntax was acceptable:
<?php
{$test->log}();
?>
But alas, it is not.
Same reasons you can't do this:
$value = $this->getArray()["key"];
or even this
$value = getArray()["key"];
Because PHP syntax doesn't do short hand very well.
This may work:
${this->Log}("Message");
But perhaps it's just easier and better to use the full call? There doesn't seem to be a way to get what you want to work on the one line.
The error in your question indicates it is looking for a function defined on the class which doesn't exist. An invokable object isn't a function, and it seems it can't be treated as one in this case.
as of php7.4 the following code works for me
($this->Log)("Message");

Categories