Php's "new" WeakMap - do Enums ever get garbage collected? - php

https://www.php.net/manual/en/class.weakmap.php
what about Enums?
You can do
enum MyEnum{
case A;
}
$wm = new WeakMap();
$wm[MyEnum::A] = 'something';
when does
$wm[MyEnum::A] become invisible? or inaccessible? if ever?
I am NOT talking about $wm. Assume $wm is always there.

An enum in php is just a (special) class with several constants, this is simple to verify:
enum MyEnum { case A; }
// enum(MyEnum::A)
// string(6) "object"
var_dump(
(new ReflectionClass(MyEnum::class))->getConstant('A'),
gettype(MyEnum::A));
Because no one can unset a constant or change its value, there is at least one reference to the enum object, the entry in the WeakMap will remain until someone manually unsets it.

Related

php - Unexpected behavior when assigning a variable with new

I am a bit confused with the following code example. I would guess that the second assignment $ins = new A(); would override the previous $ins reference.
I also don't understand the #1, #2, neither the (1),(1) in the var_dump output, I would expect at least (0),(0).
Thanks in advance
class A{
public $var = 2;
}
$ins = new A();
$aux = &$ins;
$ins->var = 3;
var_dump($aux);
echo '<br>';
$ins = new A();
$ins->var = 5;
var_dump($aux);
prints
object(A)#1 (1) { ["var"]=> int(3) }
object(A)#2 (1) { ["var"]=> int(5) }
http://www.php.net//manual/en/language.oop5.references.php
A PHP reference is an alias, which allows two different variables to
write to the same value. As of PHP 5, an object variable doesn't
contain the object itself as value anymore. It only contains an object
identifier which allows object accessors to find the actual object.
When an object is sent by argument, returned or assigned to another
variable, the different variables are not aliases: they hold a copy of
the identifier, which points to the same object.

Is it possible to dereference a reference in PHP?

I have an array I'm using as a stack to store a path through a tree. Each element points to a node in the tree and I want to pop the last element off and then set the object referred to by that element to null.
Basically:
$node = array_pop($path);
*$node = null;
assuming that PHP had a '*' operator as C-ish languages do. Right now I have the ugly solution of starting at the parent node and remembering which child I took and then setting that to null as in:
if($goLeft) {
$parent->left = null;
} else {
$parent->right = null;
}
I say this is ugly because the array containing the path is created by a public function in my tree class. I'd like to expose the ability to work directly on the nodes in a path through the tree without exposing an implementation detail that addresses an idiosyncrasy (feature?) in PHP. ATM I need to include a boolean in the return value ($goLeft in this case) just so I can workaround an inability to dereference a reference.
This is the second time I've encountered this problem, so if anyone knows a way I can do something similar to the first block of code please share!
(EDIT)
After experimenting with many permutations of &'s and arrays, it turns out that the basic problem was that I had misinterpreted the reason for an error I was getting.
I tried
$a = ($x > $y) ? &$foo[$bar] : $blah;
and got " syntax error, unexpected '&' ". I interpreted this to mean that the problem was using the &-operator on $foo[$bar]. It actually turns out that the culprit is the ?-operator, as
if($x > $y) {
$a = &$foo[$bar];
} else {
$a = null;
}
works perfectly fine. I thus went on a wild goose chase looking for a workaround for a problem that didn't exist. As long as I don't break the chain of &'s, PHP does what I want, which is to operate on the object referred to by a variable (not the variable itself). Example
$a1 = new SomeClass;
$a2 = &$a1;
$a3 = &$a2;
$a4 = &$a3;
$a4 = 42; // This actually sets $a1 to 42
var_dump($a1); // Emits 42
What messed me up is that I thought objects are passed around by reference anyway (this is wrong), so I didn't think the & was necessary if the expression resolved to an object. I mean:
class A {
public $b;
}
class B {}
$a = new A;
$a->b = new B;
$c1 = $a->b;
$c2 = &$a->b;
$c1 = 42; // Merely assigns 42 to $c1
$c2 = 42; // Assigns 42 to $a->b
It turns out that this exact issue is addressed at http://www.php.net/manual/en/language.oop5.references.php. Wish that had sunk in the first time I read it!
Very interesting question! I may have found a workaround: if you populate the array with object references, with the & operator, you can destroy the original object by setting that array value to NULL. You have to operate on the array directly, instead of using a variable returned by array_pop. After that you can pop the array to free that position (that would then contain a NULL value).
This is what I mean (based on Rocket's code):
$a=(object)'a';
$b=array(&$a);
$b[0] = NULL;
// array still contains an element
array_pop($b);
// now array is empty
var_dump($a); // NULL
http://codepad.org/3D7Lphde
I wish I could remember where I read this, but PHP works by maintaining a counter of references to a given object. You have some object (e.g. a Tree) that has a reference to some nodes. When you use array_pop, a reference to the node object is returned (i.e. an additional reference is created), but the original reference still exists. When you unset the popped reference, that is destroyed but the original object is not destroyed because Tree still has that reference. The only way to free the memory of that object is to have Tree destroy it personally (which seems to be what you're doing in the second code block).
PHP does not seem to have any method for forcing memory deallocation or garbage collection, so unless you carefully handle your references, you're stuck.
This is not possible
P.S. I'm still really confused about what you're trying to do. Rocket's explanation helps, but what is $path, and how does it relate to the second block?
Just don't assign the array_pop() return value.
php > $test = array(1, 2, 3);
php > $test2 = array(0 => &$test[0], 1 => &$test[1], 2 => &$test[2]);
php > array_pop($test2);
php > var_dump($test);
array(3) {
[0]=>
&int(1)
[1]=>
&int(2)
[2]=>
int(3)
}
php > var_dump($test2);
array(2) {
[0]=>
&int(1)
[1]=>
&int(2)
}
$one = 1;
$two = 2;
$array = array(&$one, &$two);
// magic
end($array);
$array[key($array)] = NULL;
var_dump($two);
// NULL
Reference in php allows you to change object.

How do you pass objects by reference in PHP 5?

In PHP 5, are you required to use the & modifier to pass by reference? For example,
class People() { }
$p = new People();
function one($a) { $a = null; }
function two(&$a) { $a = null; )
In PHP4 you needed the & modifier to maintain reference after a change had been made, but I'm confused on the topics I have read regarding PHP5's automatic use of pass-by-reference, except when explicity cloning the object.
In PHP5, is the & modifier required to pass by reference for all types of objects (variables, classes, arrays, ...)?
are you required to use the & modifier to pass-by-reference?
Technically/semantically, the answer is yes, even with objects. This is because there are two ways to pass/assign an object: by reference or by identifier. When a function declaration contains an &, as in:
function func(&$obj) {}
The argument will be passed by reference, no matter what. If you declare without the &
function func($obj) {}
Everything will be passed by value, with the exception of objects and resources, which will then be passed via identifier. What's an identifier? Well, you can think of it as a reference to a reference. Take the following example:
class A
{
public $v = 1;
}
function change($obj)
{
$obj->v = 2;
}
function makezero($obj)
{
$obj = 0;
}
$a = new A();
change($a);
var_dump($a);
/*
output:
object(A)#1 (1) {
["v"]=>
int(2)
}
*/
makezero($a);
var_dump($a);
/*
output (same as before):
object(A)#1 (1) {
["v"]=>
int(2)
}
*/
So why doesn't $a suddenly become an integer after passing it to makezero? It's because we only overwrote the identifier. If we had passed by reference:
function makezero(&$obj)
{
$obj = 0;
}
makezero($a);
var_dump($a);
/*
output:
int(0)
*/
Now $a is an integer. So, there is a difference between passing via identifier and passing via reference.
Objects will pass-by-reference. Built in types will be pass-by-value (copied);
What is happening behind the scenes is that when you pass in a variable that holds an object, it's a reference to the object. So the variable itself is copied, but it still references the same object. So, essentially there are two variable, but both are pointing to the same object. Changes made to objects inside a function will persist.
In the case of the code that you have there (first you need $ even with &):
$original = new Object();
one($original); //$original unaffected
two($original); //$original will now be null
function one($a) { $a = null; } //This one has no impact on your original variable, it will still point to the object
function two(&$a) { $a = null; ) //This one will set your original variable to null, you'll lose the reference to the object.
You're using it wrong. The $ sign is compulsory for any variable. It should be:
http://php.net/manual/en/language.references.pass.php
function foo(&$a)
{
$a=null;
}
foo($a);
To return a reference, use
function &bar($a){
$a=5;
return $a
}
In objects and arrays, a reference to the object is copied as the formal parameter, any equality operations on two objects is a reference exchange.
$a=new People();
$b=$a;//equivalent to &$b=&$a roughly. That is the address of $b is the same as that of $a
function goo($obj){
//$obj=$e(below) which essentially passes a reference of $e to $obj. For a basic datatype such as string, integer, bool, this would copy the value, but since equality between objects is anyways by references, this results in $obj as a reference to $e
}
$e=new People();
goo($e);

Return a method with variable list of arguments as reference in PHP

I'm trying to return a value from a method as a reference in PHP5.3. I may be going at this the completely wrong way, but I am bringing an older project up to speed with some of the newer 5.3+ features.
Below is an example I whipped up to explain what is happening:
class Foo
{
static $foobar = 5;
function &bar()
{
return self::$foobar;
}
}
// Doesn't work
//$test1 = &call_user_func_array(array("Foo","bar"),array());
// Doesn't work
//$test1 = &call_user_func_array("Foo::bar",array());
// Doesn't work
//$f = new Foo; $test1 = &call_user_func_array(array($f,"bar"),array());
// WORKS
//$test1 = &Foo::bar();
//Doesn't work
//$function = "Foo::bar";
//$test1 = &$function();
// WORKS
$f = new Foo; $test1 = &$f->bar();
$test2 = Foo::bar();
var_dump($test1);
var_dump($test2);
$test1 = 10;
echo "----------<br />";
var_dump($test1);
var_dump($test2);
var_dump(Foo::bar()); //returns 10 when working, 5 when not working
The very last Foo::bar() should return a 10, since $test1 should be a reference to Foo::$foobar when everything works.
I realize that this example also uses some funky legacy PHP calling Foo::bar and the method bar() not being specified as static, but still being able to be invoked via ::
Any help would be greatly appreciated as the only fix I have so far is to just setup a switch on the argument list, and call the method directly based upon how many arguments exist.
This is just assigning $test1 to the value of $foobar (which is 5)
$test1 = &$f->bar();
This is just overwriting the value contained in $test1 with 10
$test1 = 10;
If you want to update the value within Foo, use
$f->foobar = 10;
Doesn't it already work in PHP 5.2.5: http://codepad.org/uMEIK210 (note the 10 as final result)?
I suppose, you would like to see the 10 three times.
For that (that $test2 is also a reference to the class field) you need to specify the & on both sides:
function &bar() and $test2 =& Foo::bar();
See the docs:
Note: 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 for $myValue.
So you just need to edit one line to get the (probably) desired 3 x 10:
$test2 =& Foo::bar();
Final hint
Do not use PHP references
First of all, try declaring the function static. Also the call should be a normal call. prefixed by ampersand as already answered.
class Foo
{
static $foobar = 5;
public static function &bar()
{
return self::$foobar;
}
}
The call:
$test1 =& Foo::bar();
Also, I can't see a valid reason for referencing a static variable. A static variable is a variable that doesn't change value between calls. It basically is a "global" var enclosed in a namespace. You only need read access from outside the class, the write should be done internally, as per the encapsulation principle. No need for the reference, really..

Emulating a value type structure class in PHP

Is there any way to emulate a structure class in PHP? ie a class which passes by value and not by reference, so it can still be type hinted...
And if so, what different techniques could be used? What's the best technique?
If this is possible you could obviously create a fully type safe layer for PHP, are there such layers? Has anyone had any experience with this?
Objects are always passed by reference. The only way to make them pass as a copy is to explicitly use the clone keyword (yes, everywhere).
My recommendation would be to use an array, which are value types and thus always copied. Since you can use it as an associative array (eg string -> value), it might as well be an object. The downside is, of course, you can't use methods (but that's like a struct so you may be happy with this). There is no way to enforce type safety, however.
But with all your requirements it sounds like PHP isn't your kind of language, to be honest.
I think the easiest way is to do it like java does - have your value classes be immutable, and let all "modification" methods return a new object instead.
I don't think you can achieve that goal, only with PHP code.
You have no control on how PHP function handle parameters, and I don't see how you could make sure everything is handled the way you want, without having to change the (lower-level) code in the PHP binary and modules.
It would be pretty cool, though :)
I was playing around with anonymous's suggestion to make any mutations of the object return a new object, and this works, but it's awkward.
<?php
class FruityEnum {
private $valid = array("apple", "banana", "cantaloupe");
private $value;
function __construct($val) {
if (in_array($val, $this->valid)) {
$this->value = $val;
} else {
throw new Exception("Invalid value");
}
}
function __set($var, $val) {
throw new Exception("Use set()!!");
}
function set(FruityEnum &$obj, $val) {
$obj = new FruityEnum($val);
}
function __get($var) { //everything returns the value...
return $this->value;
}
function __toString() {
return $this->value;
}
}
And now to test it:
function mutate(FruityEnum $obj) { // type hinting!
$obj->set($obj, 'banana');
return $obj;
}
$x = new FruityEnum('apple');
echo $x; // "apple"
$y = mutate($x);
echo $x // still "apple"
. $y // "banana"
It works, but you have to use a strange way to change the object:
$obj->set($obj, 'foo');
The only other way I could think to do it would be to use the __set() method, but that was even worse. You had to do this, which is bloody confusing.
$obj = $obj->blah = 'foo';
In the end, it's probably easier to make the variables private and provide no mutators, so the only way to change a variable's "enum" value would be to create a new one:
echo $obj; // "banana"
$obj = new FruityEnum("cantaloupe");

Categories