Basically how I understand references work is
$a = 5;
$b = &$a;
$a = 10;
echo $b; // 10;
However in this bit of code I'm getting unexpected (for me, which probably has an explanation) result
class Room {
private $users = array();
public function addUser(&$user){
$this->users[] = $user;
}
}
$users = array(
1 => 'Tom',
2 => 'Hank',
3 => 'Sam',
4 => 'John'
);
$room = new Room();
$room->addUser($users[1]);
$room->addUser($users[3]);
unset($users[3]);
echo "<pre>" . print_r($room, true) . "</pre>";
echo "<pre>" . print_r($users, true) . "</pre>";
I expect, after unsetting $users[3], the only user inside of $room to be Tom, but that is not the case, both Tom and Sam are present in the object. Why is unset not affecting the object's property?
EDIT:
Even if I take things a step further with the example and create a class User the effect is still the same
class Room {
private $users = array();
public function addUser(&$user){
$this->users[] = $user;
}
}
class User {
public $name;
function __construct($name){
$this->name = $name;
}
}
$users = array(
1 => new User('Tom'),
2 => new User('Hank'),
3 => new User('Sam'),
4 => new User('John')
);
$room = new Room();
$room->addUser($users[1]);
$room->addUser($users[3]);
unset($users[3]);
echo "<pre>" . print_r($room, true) . "</pre>";
echo "<pre>" . print_r($users, true) . "</pre>";
Unset operates on symbols, not reference targets.
That is why using unset on an undefined variable doesn't raise any kind of error.
$a = 10;
$b = &$a;
unset($b); // forget the name "$b" exists.
echo $a; // 10
If you want to unset it in both places, you have to assign null to one of the variables. This is a "hard unset", as opposed to a "soft unset" which is what you are currently doing.
Also you are not assigning a reference, you're assigning a copy.
$this->users[] = &$user;
Reference Counting Basics :
A PHP variable is stored in a container called a "zval". A zval
container contains, besides the variable's type and value, two
additional bits of information. The first is called "is_ref" and is a
boolean value indicating whether or not the variable is part of a
"reference set". (...) Since PHP allows user-land references, as
created by the & operator, a zval container also has an internal
reference counting mechanism to optimize memory usage. This second
piece of additional information, called "refcount", contains how many
variable names (also called symbols) point to this one zval container.
(...)
Variable containers get destroyed when the "refcount" reaches zero.
The "refcount" gets decreased by one when any symbol linked to the
variable container leaves the scope (e.g. when the function ends) or
when unset() is called on a symbol.
Example with arrays:
<?php
$a = array(
0 => 'aaa',
1 => 'bbb',
2 => 'ccc',
);
debug_zval_dump($a);
// ... string(3) "bbb" refcount(1) ...
$b = array();
$b[0] = &$a[0];
$b[1] = &$a[1];
$a[1] = 'ddd';
debug_zval_dump($a);
// ... &string(3) "bbb" refcount(2) ...
debug_zval_dump($b);
// ... &string(3) "bbb" refcount(2) ...
unset($a[1]);
debug_zval_dump($a);
/*
array(2) refcount(2){
[0]=>
&string(3) "aaa" refcount(2)
[1]=>
&string(3) "ddd" refcount(2)
}
*/
debug_zval_dump($b);
// ... string(3) "ddd" refcount(1) ...
var_dump($a);
/*
array (size=2)
0 => &string 'aaa' (length=3)
2 => string 'ccc' (length=3)
*/
var_dump($b);
/*
array (size=2)
0 => &string 'aaa' (length=3)
1 => string 'ddd' (length=3)
*/
I think there's a slight logical problem between your desired effect and the way you try to do it.
If I understand correctly, you want to assign users to a container, then unsetting one of those user in a way that it will also be unsetted in your container. This
unset($users[3]);
unsets the value of the fourth element of your users array.
if we did $user[3] = 'foo'; the value contained in the corresponding container's entry will be set to 'foo' as well, but the container's index key itself will not get unset, or affected by the reference, because it is not part of the referenced value
If you want to unset the user, either you keep track of which index key is assigned to which user in your container and then delete users with this index key, or you set the value of $users[3] to null (or whatever suits your needs) and skip the null values when dealing with your container
You can change a value of arrays, like this:
CODE:
private $users = array();
public function addUser(&$user){
$this->users[] = &$user;
}
}
$users = array(
1 => 'Tom',
2 => 'Hank',
3 => 'Sam',
4 => 'John'
);
$room = new Room();
$room->addUser($users[1]);
$room->addUser($users[3]);
$users[3] = "AAA123";
echo "<pre>" . print_r($room, true) . "</pre>";
echo "<pre>" . print_r($users, true) . "</pre>";
OUTPUT:
Room Object
(
[users:Room:private] => Array
(
[0] => Tom
[1] => AAA123
)
)
Array
(
[1] => Tom
[2] => Hank
[3] => AAA123
[4] => John
)
But delete it's not possible this way... I don't know how to explain, so just give example:
$a = 10;
$b = &$a;
unset($a);
echo $b; // 10
Then you deleting variable name, you not delete zval(container), until refcount reach 0... then "Garbage Collection" do all work and delete zval...
So method unset() remove variable name only in this case...
Be careful.
You are passing to addUser() a reference to the string 'Tom' allocated while building the array $users.
First, addUser() should read $this->users[] =& $user;, otherwise you will be copying the value into $this->users[] instead of sharing the reference.
Now, both $users and Room::$users share the same objects, however unset($users[3]) removes the element mapped by the index 3 from the array, it does not destroy the mapped object.
Related
I have a weird behavior when assigning an existant object to an array.
In the example below, I have a class that contains one property. I create a first array with 3 instances (let's call them 2-4-6) and then a second array with 2 other instances using one object of the first array (instance 4). While modifying the values of the objects in second array to create 2 new instances (ie instances 3-5), the instance 4 is also modified. In consequence, at the first array query I get the right values (2-4-6) but after creating the second array I get the modified value (2-5-6). I would expect that the assignment operator copied the object, but instead it creates a reference to the instance. In the example I can get rid of this issue by explicitly calling clone, but in a larger scale, this isn't work (wrong code optimization?). Any clue (or good practice) of how to avoid this issue?
Thanks!
<?php
class TestBase
{
private int $m_test = 0;
public function SetTest(int $v)
{
$this->m_test = $v;
}
public function GetTest() : int
{
return $this->m_test;
}
}
function getNewList(TestBase $ref) : array
{
$newlist = [3 => new TestBase(), 5 => $ref];
$newlist[3]->SetTest(3);
$newlist[5]->SetTest(5);
return $newlist;
}
$listOfTest = [2 => new TestBase(), 4 => new TestBase(), 6 => new TestBase()];
$listOfTest[2]->SetTest(2);
$listOfTest[4]->SetTest(4);
$listOfTest[6]->SetTest(6);
foreach ($listOfTest as $test)
{
echo $test->GetTest().'<br>';
}
// 2
// 4
// 6
//$ref = clone $listOfTest[4];
$ref = $listOfTest[4];
$newList = getNewList($ref);
foreach ($listOfTest as $test)
{
echo $test->GetTest().'<br>';
}
// 2
// 5
// 6
?>
The problem is in the way the PHP uses arrays. Array assignment by reference
only works if both the argument and the lvalue are references to arrays. If an object is passed in, it will be copied and a reference to it will be set in both places. The subsequent reassignment of the lvalue will not have any effect on the original array.
Referencing an existing array and then copying it using clone can work around this. The example below shows how this can be done, but you could also use a factory or some other means to create a new array that has references to all of the existing elements (by calling get_object_vars() on each).
<?php
function getNewList(TestBase $ref) : array
{
$newlist = [3 => clone $ref, 5 => clone $ref];
$newlist[3]->SetTest(3);
$newlist[5]->SetTest(5);
return $newlist;
}
//$ref = clone $listOfTest[4];
$ref = &$listOfTest[4];
var_dump($ref); // TestBase Object (1) (2) { ["m_test"]=> int(5) }
//$newList = getNewList($ref);
foreach ($listOfTest as &$test)
{
echo "before: ".$test->GetTest().'<br>'; // 2, 4, 6 in output here!
var_dump($test); // TestBase Object (1) (2) { ["m_test"]=> int(4) }
echo "after: ".$test->GetTest().'<br>'; // 2, 4, 6 in output here!
var_dump($test); // TestBase Object (1) (2) { ["m_test"]=> int(4) }
}
?>
I have two PHP arrays in a section of a website I'm building for a class. One contains data that I created with a simple foobar array bit, and the other is created from a fetchAll() PDO call. I can't access the PDO array's element for some reason, so I created a sample array and called var_export() on both arrays to see if there were any differences. Here is the code:
if ($dao->userExistsLogin($_POST["email"], $_POST["password"]))
{
$useremail = $_POST["email"];
$_SESSION["authenticated"] = "true";
$logger->LogDebug("email from post array in login handler: " . $_POST["email"]);
$user_results = $dao->getIDFromEmail($useremail);
$logger->LogDebug(print_r($user_results, true));
$array = [
"foo" => "bar",
"bar" => "foo",
];
$logger->LogDebug(var_export($array, true));
$logger->LogDebug(var_export($user_results, true));
$logger->LogDebug(gettype($user_results));
$_SESSION['user_id'] = $user_results['id'];
header("Location: http://localhost/WIWO/user_account.php");
}
The GetIDFromEmail function is here:
public function getIDFromEmail($email)
{
$conn = $this->getConnection();
$getIDFromEmailQuery = "SELECT * FROM user WHERE email=:email";
$query = $conn->prepare($getIDFromEmailQuery);
$query->bindParam(":email", $email);
$query->execute();
$result = $query->fetchAll(PDO::FETCH_ASSOC);
$this->logger->LogDebug(print_r($result, true));
return $result;
}
And here's what's in my logger from the var_export() commands:
2020-10-31 17:57:10 - DEBUG --> array (
'foo' => 'bar',
'bar' => 'foo',
)
2020-10-31 17:57:10 - DEBUG --> array (
0 =>
array (
'id' => '251',
'email' => 'heck',
'institution' => 'heck',
'access' => '1',
),
)
Why can I not access the second array at $_SESSION['user_id'] = $user_results['id'];? For some reason that line totally fails to do anything. I know the session is started and correct because other variables stored in the SESSION superglobal are showing up correctly, but the user_id never gets set. Does it have something to do with "0 =>" showing up in the var_export() of the second array? I notice that doesn't show up in the first array - is the second array malformed or something?
You're using PDOStatement::fetchAll(), which will always return an array of rows. Instead you might want to use PDOStatement::fetch() which will return precisely one row.
I haven't seen the userExistsLogin() method, but you could probably simplify your code if you have that method return the user id on success, and something falsey (e.g. 0, FALSE or NULL) on error. This will not only reduce code complexity, but it will reduce database load as well.
Hi guys I need a bit of help here,
My variable $a has value [{"id":"[1, 2, 3]"}] of type object, now from this I want the first id i.e. in this case I want 1. How can I fetch ut from variable $a?
When I var_dump it prints as below
class Illuminate\Support\Collection#1230 (1) {
protected $items =>
array(1) {
[0] =>
class stdClass#1221 (1) {
public $a =>
string(9) "[1, 2, 3]"
}
}
}
Thank you.
What is the class of $a ?
Is it an extend of Illuminate\Database\Eloquent\Model ?
Maybe this :
$firstId = current($a->id);
EDIT :
I don't see the id on your dump.
current(json_decode($a->first()->id))
or
current(json_decode($a->first()->a))
Can you access ?
echo $a->id; // outputs strings "[1,2,3]"
if yes, then just decode it by using json_decode
$array = json_decode($a->id);
$firstElement = current($array);
Lets say that I have a View that uses 3 variables, while developing on Laravel 4:
<ul>
<li>Name: {{name}}</li>
<li>Surname: {{surname}}</li>
<li>Age: {{age}}</li>
<ul>
In my controller, I could be adding the data to the view the following way.
$context = array('name' => $name, 'surname' => $surname, 'age' => $age);
return View::make('myview');
But this code isnt completely DRY, since I am writing twice the name of the variable. It would be optimal if I could do:
$context = array($name, $surname, $age);
return View::make('myview', $context);
This code seems tighter. But unfortunately, the view will give me an error "Undefined variable: name".
Is there any way to achieve this with Laravel?
UPDATE: Another option, as stated in some answers is using compact.
$context = compact('name', 'surname', 'age')
return View::make('myview', $context);
But the problem here is that you have to create all your context variable in one go. And programming is not always so convenient sadly. Sometimes you will want to be adding your elements to the context, as you go progressing through the function.
For instance:
$context = array($name, $surname, $age);
if ($age > 18){
$adult = "He is adult";
array_push($context, $adult);
}
return View::make('myview', $context);
So as you see, if I could use arrays, it wouldnt be a problem because I could push new elements into the $context with array_push. But I can not use arrays that way, unfortunately. And compact wouldn't allow me to push elements inside the context, would it?
You use compact(), a native PHP function (see docs):
compact — Create array containing variables and their values
For each of these, compact() looks for a variable with that name in the current symbol table and adds it to the output array such that the variable name becomes the key and the contents of the variable become the value for that key. In short, it does the opposite of extract().
Any strings that are not set will simply be skipped.
For example, to create an array with keys of name, surname, age, with their values respectively:
$name = 'foo';
$surname = 'bar';
$age = 99;
$context = compact("name", "surname", "age");
return View::make('myview', $context);
If you var_dump($context) you would get:
array(3) {
["name"] => string(3) "foo"
["surname"] => string(3) "bar"
["age"] => int(99)
}
Which is what you want to pass into View::make().
Update:
To respond the part of question about using array_push(). I don't think you can use array_push() regardless of compact() anyway. The View::make() still requires a two-dimensional array with key => values. Say you have this array:
$context = array(
'name' => 'foo',
'surname' => 'bar',
'age' => 99,
);
Doing an array_push($context, $adult) would give you:
array(4) {
["name"] => string(3) "foo"
["surname"] => string(3) "bar"
["age"] => int(99)
[0] => string(11) "He is adult"
}
You get [0] => string(11) "He is adult" instead of ["adult"] => string(11) "He is adult". You would not get your expected result in your view using array_push()
To answer "Sometimes you will want to be adding your elements to the context, as you go progressing through the function.", I say there are two ways:
1st approach: Append $context as you go. Similar to your example:
$context = compact("name", "surname", "age");
if ($age > 18) {
$context['adult'] = "He is adult"; // You need to do this way because array_push() does not support setting your own key.
}
return View::make('myview', $context);
Pros: No need to worry whether you have added your variable to the compact() yet.
Cons: To know what is being passed to your view, you need to scan your whole method for key/value that you appended to $context.
2nd approach: Prepare all data then compact them into $context only right before sending to the view.
$name = 'foo';
$surname = 'bar';
$age = 99;
if ($age > 18){
$adult = "He is adult";
}
$context = compact("name", "surname", "age", "adult");
return View::make('myview', $context);
Pros: Single point of compiling view context. You can easily see all variables that are compacted and sent to the view.
Cons: Misspellings are harder to detect. compact() can become really long if you are passing a lot of variables.
$order_temp = $this->mdl_admin->get_latest_order_id($_POST['parent_id']);
if ($order_temp) {
$order = (string)$order_temp->order++;
var_dump($order);
die();
}
This code above produce this error:
Message: Attempt to increment/decrement property of non-object
And the vardump is string '' (length=0)
If I do just something like this to vardump the variable:
$order_temp = $this->mdl_admin->get_latest_order_id($_POST['parent_id']);
if ($order_temp) {
var_dump($order_temp);
die();
}
the output is :
array (size=1)
0 =>
object(stdClass)[28]
public 'id' => string '16' (length=2)
public 'name' => string 'sssssssssssssss' (length=15)
public 'slug' => string 'aaaaaaaaa' (length=9)
public 'title' => string 'aaaaaa' (length=6)
public 'body' => string '<p>asdas asd asd </p>' (length=21)
public 'order' => string '1' (length=1)
public 'parent_id' => string '5' (length=1)
I just want to add 1 to the $order_temp->order so if e.g. the $order_temp->order = 2 the result $order = 3.
What am I doing wrong?
$order_temp is not an object, as the error message says. When you try to access (or in this case, increment) a property of something that isn't an object (and so does not have properties), you get this error.
Not knowing what $this->mdl_admin->get_latest_order_id does, I can only go so far, BUT, I can tell you to use explicit comparisons in if statements, and that your type coercion isn't doing what you expect.
Use explicit comparison:
if (is_object($order_temp) === true)
.. that might not be the cause of your bug, but that's good practice.
Your use of ((string)) in assignment coerces the presumed object variable to a string, which you then try to access property of. This is not the droid you are looking for. It appears, from PHP's perspective, that you want to do this:
"0"->order++;
...which doesn't make sense.
PHP uses type juggling[doc]. Whether your object's property order started out as a string or not, as soon as you use the increment operator ++, PHP juggles the type to become an integer. If you want a string, all you have to do is start treating it like a string:
$order_temp->order++; // <-- variable is an integer, $order_temp->order === 1
$order_temp->order .= " and bob's your uncle"; // <-- variable is a string, $order_temp->order === "1 and bob's your uncle"
For that reason, type coercion is seldom necessary in PHP.
Finally, you can do this exactly in the way that you're trying, but you'll have to understand the way the increment ++ operator works.
$number = 0;
$output = $number++;
echo "Output: ".$output; // Output: 0
$number = 0;
$output = ++$number;
echo "Output: ".$output; // Output: 1
... by placing the increment operator at the FRONT of the assignment, the return will be the incremented variable after the increment operation. If you put the operator at the end, then the return is the variable before the increment operation.
That means you could do this:
$order_number_as_string = (string) (++$one_order->order);
By enclosing the increment operator in parenthesis and type casting the outside, plus putting the increment operator as a prefix, you will get the expected results.
Documentation
Increment/Decrement operators - http://php.net/manual/en/language.operators.increment.php
Type juggling - http://php.net/manual/en/language.types.type-juggling.php
settype - http://php.net/manual/en/function.settype.php
<?php
class Test
{
public $order = 10;
}
$order_temp = new Test();
$order = (string)$order_temp->order++;
var_dump($order);
//string(2) "10"
$order = (string)$order_temp->order;
var_dump($order);
//string(2) "11"
?>
Your $order_temp is not an object. So that is what is causing the problems. Also note that you might not get the expected results, even if it works.
Your $order will contain the OLD value. See my test.
UPDATE
The var_dump of your $order_temp shows that its an array. You could use
$order_temp[0]->order++;
$order = $order_temp[0]->order
var_dump($order);