Getting Advanced with Variables

Spread the news

Variables are pretty straightforward in PHP. Anything that starts with a $ is a variable.  However, there are some things about them that can be somewhat confusing.  Let’s do an overview of Variables in PHP.

Type Juggling

In PHP, variables will automatically convert to whatever type is needed.  Most of these transformations are very natural and it allows programmers to spend the majority of time working on the application logic.  However, every now and then some of the automatic conversions can get you in trouble if you’re not prepared.

For example, if you try to multiply an integer and a float together in a strongly-typed language, you will get an error. PHP allows you to skip the conversion of the integer to a float and does that automatically for you.  I also love that I can make a string, and append an integer to it without doing anything to make it a string. This is great for showing statistics with labels to users, for example.

However, if you try to cast a string to an integer you might run into trouble.  For example “1000 apples” will cast to the integer 1000, and even “5.3e2” will cast to 530! However, “$50.00” will not convert to a float 50, but to int 0! Also, if the number is formatted it will also do some funky things.  “1,345.30” will cast to int 1, and so will “1.345,3” (following the European standard). If you want to do some math with these formatted strings, you’ll have to manually modify them to remove the commas. a simple $num = floatval(str_replace(',', '', $string)); will do!

If you are ever wondering what type a particular variable has, you can use the gettype($var) function to find out. If you want to force the juggling to happen, you can simply put the type in parenthesis before the variable or use the settype($var, $type) function.

Passing by Reference

Another thing that is commonly misunderstood by developers is how passing a variable by reference works.  This is very similar to “pointers” in other languages. By default when you say that one variable equals another variable ($a = $b), the second variable is copied into the first one. You now have two separate variables that each have their own value. This is the default because it is typically what developers want.

However, variables can also be passed by reference! This means that the second variable and the first variable would point to the same spot in memory – if you change one of them, the other changes too — automatically. Passing by reference in PHP is always done with the prefix &. This ampersand symbol goes directly in front of the $, but it can sometimes be confusing where to put it in the grand scheme of things.

$a = 1;
$b = $a;
$b = 2;
// $a === 1
// $b === 2

$a = 1;
$b = &$a;
$b = 2;
// $a === 2
// $b === 2

$a = 1;
// $a === 1
// $b === 1

Adding the & before the $ causes the $b and $a change with one another.

This gets fun when you start passing variables by reference with functions and classes as it allows you to mix the scope of the variables between multiple methods or even classes.  Each method/class will have it’s own variable (pointer) which is still accessible in the same scope it usually is. However, when you change the pointer in one scope, you are inherently changing the value of the one in the other scope. You will often see this in PHP documentation to show that a function changes the variable that you are passing into it, such as array_pop which removes a value from the end of the array.  After you use array_pop on an array, the array has changed.

I have used this to get variables between classes such as when dealing with WebSockets and Ratchet. In that example I proposed using a Router to pass connected client objects into a single Controller. However, if you want to have multiple Controllers, then you would have to store the clients in the Router and pass them into all the Controllers by reference, so that as clients connect/disconnect, all the controllers can automatically access updated information about clients that are connected.

<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class Router implements MessageComponentInterface {
	
	public $controller1;
	public $controller2;
	public $clients;
	
	public function __construct(){
		$this->clients = new \SplObjectStorage();
		$this->controller1 = new Controller1($this->clients);
		$this->controller2 = new Controller2($this->clients);
	}
	
	...
	
}

class Controller1 {
	
	/* @var \SplObjectStorage */
	public $clients;

	public __construct(&$clients){
		$this->clients = &$clients;
	}
	
	...
	
}

class Controller2 {
	
	/* @var \SplObjectStorage */
	public $clients;

	public __construct(&$clients){
		$this->clients = &$clients;
	}
	
	...
	
}

This will allow changes to always update and be available in three (or more) different contexts! Notice that Controller1 and Controller2 both have the magic & in the incoming variables of the constructor, and then again when assigning the pointer to the class property. This causes the class property to end up being a pointer as well. If you leave out any of the &s, the methods in the controllers will never be able to send a message to any clients that need to be found in the client list (such as a broadcast message).

Passing Variables into an anonymous function’s scope

Variables can also be passed into the scope of an anonymous function using the use language construct. When creating the function you simply pass in some variables which will remain accessible in the scope of the anonymous function. These are useful because you might want to pass in a variable to the function at creation time, but not every time you run the function. What makes these extra fun is that they can also pass variables by reference! If you don’t pass by reference, the internal scope of the function will get a static copy of the variable to use.

These can also be used in a WebSockets application. If you want the server to make an HTTP call to get some data and pass it to all the clients, but only whenever the value changes, you can accomplish this with a Periodic Timer (which takes an anonymous function).

<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Router;

//load composer
require 'vendor/autoload.php';

//preload bitcoin info
$ticker = json_decode(file_get_contents('https://blockchain.info/ticker'));
$btc = $ticker->USD->last;
$coins = ['bitcoin'=>$btc];

$app = new Router($coins);
//setup server
$server = IoServer::factory(
	new HttpServer(
		new WsServer(
			$app
		)
	),
	8100
);
$server->loop->addPeriodicTimer(10, function() use (&$app, &$coins){
	$ticker = json_decode(file_get_contents('https://blockchain.info/ticker'));
	$usd = $ticker->USD->last;
	if ($coins['bitcoin']!==$usd){
		$coins['bitcoin']=$usd;
		$message = json_encode(['action'=>'bitcoin', 'price'=>number_format($usd,2), 'error'=>false]);
		foreach ($app->controller->clients as $conn){
			$conn->send($message);
		}
	}
});

//run server
$server->run();

In this code a WebSocket server is created with a Router. You would want to pass $coins into the Router by reference so it stays up to date in the router/controllers. Then, the periodic timer will fire every 10 seconds, getting the current cost of bitcoin and sending a message to all the currently connected users with the updated price if it has changed. Your server script might be making a lot of calls for this, but at least you’re able to proxy it for all your users so they don’t have to hit the blockchain servers, too.

Bonus points for those of you who notice the inherent problem with this code and it’s use of blocking functions. Anyone care to fix it so it is using an asynchronous call to get the cost of bitcoin? I would love to see your solutions in the comments below!

As always, I’m happy to help if you have some questions about this topic! Add a comment below and I’ll be sure to reply as soon as possible.


Spread the news

3 Comments

  1. Nice post John! I’d only add a information about the “Passing by Reference” topic. The way PHP works, all primitive types are actually handled with copy-on-write technique. That is, given an array $a = [“foo”] and the statement $b = $a, internally $a and $b are actually the same variable, until $a or $b change it’s value.

    This is defined in PHP Manual of debug_zval_dump function (http://php.net/manual/en/function.debug-zval-dump.php) and also available at: http://www.phpinternalsbook.com/zvals/memory_management.html#reference-counting-and-copy-on-write

  2. Tomasz Sawicki

    Thank you for your write-up.

    There is one thing worth mentioning about objects. When you assign an object instance to a variable, this variable will hold only an “object identifier”, not the actual object. To get to the actual object and its methods and properties you have to use “->” on a variable. That means, if you pass such object variable without reference, you are not copying the whole object but only this “object identifier” which still point to the same object instance. So in most cases, you don’t need to pass object variables with “&”, if you want them to always hold the same instance.

    So, in your example with SplObjectStorage, if you won’t be assigning new instance of this class later in the code, but only manipulate items in this storage, there is no need to use “&”.

    Before PHP 5 came out, it was a different story, but no one should use it today anyway.

Leave a Reply

Your email address will not be published. Required fields are marked *