Can anyone please explain to me why the following code does not set the values on the array as expected? $_SESSION['foo'] stays empty, even after assigning time() and rand(). I've checked, the __get accessor method is actually called when assigning the variables but they aren't stored for one reason or another.
$test = Session::getSession('test');
$test->foo = array();
$test->foo[] = time();
$test->foo['baz'] = rand(1,9);
var_dump($_SESSION);
Using this simple Session wrapper
class Session
{
protected $namespace = null;
public static function getSession($namespace)
{
return new Session($namespace);
}
public static function destroySession($namespace)
{
if(isset($_SESSION[$namespace])) {
unset($_SESSION[$namespace]);
return true;
}
return false;
}
private function __construct($namespace)
{
$this->namespace = $namespace;
if(!isset($_SESSION[$namespace])) {
$_SESSION[$namespace] = null;
}
}
public function &__get($name)
{
return (isset($_SESSION[$this->namespace][$name])) ? $_SESSION[$this->namespace][$name] : null;
}
public function __set($name, $value)
{
$_SESSION[$this->namespace][$name] = $value;
}
}
In case it might be relevant, i'm using php 5.3.6
I 'm not sure if this can be made to work at all.
For one, to return by reference you should add the & operator at the call site as well. I 'm not sure how that might be possible without screwing up the nice syntax you 're trying to achieve.
Also, you cannot return expressions by reference (only variables). So this won't work:
public function &__get($name)
{
return (isset($_SESSION[$this->namespace][$name]))
? $_SESSION[$this->namespace][$name]
: null;
}
At the very least it should be written as
public function &__get($name)
{
$value = isset($_SESSION[$this->namespace][$name])
? $_SESSION[$this->namespace][$name]
: null;
return $value;
}
Related
I am attempting to write a simple wrapper class to get the value of a global variable. I was thinking to use it like this:
print_r($class->session()->getAll());
print_r($class->cookie()->getAll());
Here's what I have:
class GlobalVars() {
private $current;
public function session() {
$this->current = 'SESSION';
return $this;
}
public function cookie() {
$this->current = 'COOKIE';
return $this;
}
public function getAll() {
return $_{$this->current}; // Obviously wrong
}
public function get($key) {
if (!isset($_{$this->current}[$key])) { // Obviously wrong
return false;
}
return $_{$this->current}[$key]; // Obviously wrong
}
public function set($arr) {
if (is_array($arr)) {
foreach ($arr as $k => $v) {
$_{$this->current}[$k] = $v;
}
}
}
}
$class = new GlobalVars();
print_r($class->session()->getAll());
With this example, I get a Notice: Undefined variable: _ message. What do I need to modify to get this to work?
In my opinion this is just a simple syntactical error you have made. What you did:
public function getAll() {
return $_{$this->current}; // Obviously wrong
}
But the correct way to emulate a variable from string is:
public function getAll() {
return ${"_".$this->current};
}
I have tested it. Similar behaviour for the other variables. More information on variable variables in the docs: http://php.net/manual/en/language.variables.variable.php
It's not gonna work like this. You need variable variables:
$var = "_{$this->current}";
var_dump($$var['rnd']);
Example
It's very bad way to use varVars, because it's not readable and usually IDE does not know what are you using and it's easy to get buggy code.
I have a class for session handling that uses object overloading for __GET and __SET, I've been having issues with arrays and read to assign get by reference, such as &__GET
The problem is I can't update the values. For example, let's say I have this:
$session->item['one']['name']
I'd like to change it, by assigning it a new value; $session->item['one']['name'] = 'new value' However, it doesn't change.
Any ideas how to work around this? Below is the code, thank you!
class Session
{
private $_session = array();
public function __construct()
{
if(!isset($_SESSION)) {
session_start();
}
$this->_session = $_SESSION;
}
public function __isset($name)
{
return isset($this->_session[$name]);
}
public function __unset($name)
{
unset($_SESSION[$name]);
unset($this->_session[$name]);
}
public function &__get($name)
{
return $this->_session[$name];
}
public function __set($name, $val)
{
$_SESSION[$name] = $val;
$this->_session[$name] = $val;
}
public function getSession()
{
return (isset($this->_session)) ? $this->_session : false;
}
public function getSessionId()
{
return (isset($_SESSION)) ? session_id() : false;
}
public function destroy()
{
$_SESSION = array();
session_destroy();
session_write_close();
unset($this->_session);
}
}
In your constructor, change $this->_session = $_SESSION; to $this->_session = &$_SESSION; so you're getting a reference to it inside of your class.
I'm wondering if it's possible, and in case it is, how shoud I achive that:
$this->id <-- i have such thing. but to make it more usable i'd like to have $this->(and here to change the values)
for ex: I might have $this->id $this->allID $this->proj_id
how can I make so that actually I have $this->($myvariable here, that has a unique name in it)?
You can simply use this:
$variable = 'id';
if ( isset ( $this->{$variable} ) )
{
echo $this->{$variable};
}
Here is the solution : http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members
An example of using it is here :
class myClass {
/** Location for overloaded data. */
private $myProperties = array();
public function __set($name, $value)
{
$this->myProperties[$name] = $value;
}
public function __get($name)
{
if (array_key_exists($name, $this->myProperties))
{
return $this->data[$name];
}
}
}
You should check out the variable variables manual on the PHP site.
With that, it could look like:
<?php
echo ${'this->'.$yourvariable};
?>
I prefer to use call_user_func and pass the parameters as array instead.
public function dynamicGetterExample()
{
$property = 'name'; // as an example...
$getter = 'get'.ucfirst($property);
$value = call_user_func(array($this,$getter));
if (empty($value)) {
throw new \Exception('Required value is empty for property '.$property);
}
return $value;
}
I'm trying to hold onto a variable reference for later use.
Not certain this is even possible, but I'm hoping I can initialize an array element, and reference it with a variable. Then, set the value of said array element to something, therefore making the value accessible from the referenced variable.
For example, this works:
class Test{
private $_vars = array();
public function bind($key, &$var){
$this->_vars[$key] = &$var;
return $this;
}
public function fetch($key, &$var){
$var = $this->_vars[$key];
return $this;
}
}
$test = new Test();
$string_set = 'This is a string';
$test->bind('string', $string_set)
->fetch('string', $string_get);
var_dump($string_get);
// expected: string(16) "This is a string"
// actual: string(16) "This is a string"
Now here's the problem; the ordering of method calls. I can't have the call() function returning a reference to $this, as the call() function needs to pass up the return value of the stored anonymous function (otherwise I'd reorder the calls to be ->call()->fetch() instead of ->fetch()->call())
Anyways, the fetch() method should be setting the appropriate element by key in $_vars to NULL (to empty any existing value, or initialize it, whichever) and then referencing that element to the passed $var.
When the anonymous function is called (after the fetch() binding is done), it calls bind(), now binding the element in $_vars to whatever (a $string_set containing This is a string in this case) If my logic is correct, the fetch() bound variable ($string_get in this case) should now reference the array element in $_vars which is referencing $string_set which contains This is a string.
Doesn't seem that way though. Here's the code that's failing (stripped down for brevity, but all the important parts are there)
class Test{
private $_vars = array();
private $_function;
public static function factory(){
return $test = new self(function() use(&$test){
$string_set = 'This is a string';
$test->bind('string', $string_set);
return true;
});
}
private function __construct($function){
$this->_function = $function;
}
public function bind($key, &$var){
$this->_vars[$key] = &$var;
return $this;
}
public function fetch($key, &$var){
$this->_vars[$key] = null;
$var = &$this->_vars[$key]; // edited; was not assigning by reference
return $this;
}
public function call(){
return (bool) call_user_func($this->_function);
}
}
$return = Test::factory()
->fetch('string', $string_get)
->call();
var_dump($return, $string_get);
// expected: bool(TRUE), string(16) "This is a string"
// actual: bool(TRUE), NULL
Am I chasing daisies here, is this even possible? Either way, I appreciate and thank you in advance for even glancing at this problem, any insight is really appreciated.
Edit: the line in fetch() - $var = $this->_vars[$key]; wasn't assigning the array element by reference. I've edited it now to $var = &$this->_vars[$key];, though it seemingly has no effect.
Bonus: If this problem is solvable, that's obviously great; I'm actually hoping that bind() can take $var by value, rather than by reference. The method signature would be changed to something like set($key, $value). Anyways, thanks again in advance.
To elaborate for the seemingly curious (looking in your direction #Tomalak) I'll provide the more complete class, and usage scenario:
class Action{
private static $_cache = array();
private static $_basePath;
private $_vars = array();
private $_function;
public static function setBasePath($basePath){
$basePath = rtrim($basePath, '/') . '/';
if(!is_dir($basePath)){
// throw exception, $basePath not found
}
self::$_basePath = $basePath;
}
public static function load($actionPath){
$actionPath = self::$_basePath . $actionPath;
if(array_key_exists($actionPath, self::$_cache)){
return self::$_cache[$actionPath];
}
if(!is_file($actionPath)){
// throw exception, $actionPath not found
}
$action = call_user_func(function() use(&$action, $actionPath){
return require($actionPath);
});
if(!($action instanceof self)){
// throw exception, $action of invalid type
}
self::$_cache[$actionPath] = $action;
return $action;
}
public function __construct($function){
if(!is_callable($function)){
// throw exception, $function not callable
}
$this->_function = $function;
}
public function bindReturn($key, &$var){
$this->_vars[$key] = &$var;
return $this;
}
public function fetchInto($key, &$var){
$this->_vars[$key] = null;
$var = &$this->_vars[$key];
return $this;
}
public function run(){
return (bool) call_user_func_array($this->_function, func_get_args());
}
}
############################################################################
// actions/test.php
return new Action(function($name)
use(&$action){
if($name == 'Alice'){
return false;
}
$data = "Hi, my name is {$name}.";
$action->bindReturn('data', $data);
return true;
});
############################################################################
// index.php (or whatever)
$result = Action::load('actions/test.php') // loaded
->fetchInto('data', $data)
->run('Alice');
// Failed
echo $result
? 'Success - ' . $data
: 'Failed';
$result = Action::load('actions/test.php') // called from cache
->fetchInto('data', $data)
->run('Bob');
// Success - Hi, my name is Bob
echo $result
? 'Success - ' . $data
: 'Failed';
What you want do is simply not possible (at least with referencces), because you cannot "redirect" a reference. Here's what happens:
$instance->fetch('foo', $myVar);
public function fetch($key, &$var){
// Here $var is a reference to $myVar.
$var = &$this->_vars[$key]; // now $var is a reference to $this->_vars[$key]
// it is not connected to $myVar anymore.
}
Here's what you can do: You can pass fetch() a reference to an array and set an element in that array to be a reference to $this->_vars[$key] or you can pass fetch() an object and set a member variable to be the reference.
Oh, sry missed the obvious: You can of course just use your bindReturn() function in the use-case you presented. That would work without problems.
Looks like you have problem with
public function fetch($key, &$var){
$this->_vars[$key] = null;
$var = $this->_vars[$key];
return $this;
}
If you want to remove the key, don't set it to null, unset it:
Edit: changed the code to avoid uninitialized variable exception.
public function fetch($key, &$var){
if(isset($this->_vars[$key]))
{
$var = $this->_vars[$key];
unset($this->_vars[$key]);
}
else
{
$var = null;
}
return $this;
}
How can i perform a function once a variable's value has been set?
say like
$obj = new object(); // dont perform $obj->my_function() just yet
$obj->my_var = 67 // $obj->my_function() now gets run
I want the object to do this function and now having to be called by the script.
Thanks
EDIT
my_var is predefined in the class, __set is not working for me.
Use a private property so __set() is invoked:
class Myclass {
private $my_var;
private $my_var_set = false;
public function __set($var, $value) {
if ($var == 'my_var' && !$this->my_var_set) {
// call some function
$this->my_var_set = true;
}
$this->$var = $value;
}
public function __get($var, $value) {
return $this->$name;
}
}
See Overloading. __set() is called because $my_var is inaccessible and there is your hook.
I'd recommend to create a setter function for $obj and include the relevant function call there. So basically your code would look somehow like this:
$obj = new ClassOfYours();
$obj->setThatValue("apple");
Of course you would have to take care that all assignments to ThatValue need to be
done through that setter in order make it work properly. Assuming that you're on php5 I'd set that property to private, so all direct assignments will cause an runtime error.
A good overview about OOP in php can be found in this article on devarticles.com.
HTH
To acheive exactly what you describe, you'd have to use a magic setter.
class ObjectWithSetter {
var $data = array();
public function my_function() {
echo "FOO";
}
public function __set($name, $value) {
$this->data[$name] = $value;
if($name == 'my_var') {
$this->my_function();
}
}
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
/** As of PHP 5.1.0 */
public function __isset($name) {
return isset($this->data[$name]);
}
public function __unset($name) {
unset($this->data[$name]);
}
}
Assuming you want to call my_function() once you set a value, that case you can encapsulate both the operations into one. Something like you create a new function set_my_var(value)
function set_my_var(varvalue)
{
$this->my_var = varvalue;
$this->my_function();
}