In a previous post, I talked about singleton services that were retrieved from the container and how they could impact queue workers. At the end of the post I added a small comment on statics and how they could also cause trouble. I’d like to go into some more details on class properties.
Two Types of Class Properties
Maybe class property isn’t the correct wording here, but that’s what I’m going to refer to them as. In PHP we have instance properties which are tied to an instance of an object, and we have static properties which are tied to a class.
Instance Properties
These are properties that require an instance to reference. Memory is allocated for each instance, and for each property on that instance.
class Example {
public $propertyOne = 'one';
}
$example = new Example;
echo $example->propertyOne; // 'one'
Static Properties
These are properties that are tied to the class. They don’t require an instance of the class. Only a single memory location is allocated for the reference. You could think of these as global variables as you can pretty much reference them from anywhere.
class Example {
public static $propertyOne = 'one';
}
echo Example::$propertyOne; // 'one'
The zval
PHP uses zval
to store objects and references in memory. If you have the xdebug tool installed you can use the xdebug_debug_zval
method to gather some information about specific objects.
Here’s a new version of the example class:
class Example {
public $propertyOne;
public static $propertyTwo;
public function __construct($propertyOne)
{
$this->propertyOne = $propertyOne;
}
}
If we use it in the following way:
$example1 = new Example('foo');
$example2 = new Example('bar');
print xdebug_debug_zval('example1');
print xdebug_debug_zval('example2');
The following results are printed out:
example1: (refcount=1, is_ref=0)=class Example {
public $propertyOne = (refcount=0, is_ref=0)='foo'
}
example2: (refcount=1, is_ref=0)=class Example {
public $propertyOne = (refcount=0, is_ref=0)='bar'
}
The refcount refers to the number of times a variable is referenced. Notice that both $example1
and $example2
are only referenced once.
However, if I were to modify the code a bit:
$example1 = new Example('foo');
$example2 = new Example('bar');
$example3 = $example1;
You can see that the refcount increases on $example1
.
example1: (refcount=2, is_ref=0)=class Example {
public $propertyOne = (refcount=0, is_ref=0)='foo'
}
example2: (refcount=1, is_ref=0)=class Example {
public $propertyOne = (refcount=0, is_ref=0)='bar'
}
PHP actually uses this value to determine which references should be cleaned up during garbage collection
Something else to note, is that the static
property $propertyTwo
is not shown in the output of xdebug_debug_zval
. This is because it is not tied to the instance, and should not be accessible from the instance.
PHP Variable Life Cycle
You can read more about the garbage collection process to get an idea of how instance variables are cleaned up during garbage collection. Something to note is that statics are not garbage collected. Static variables are cleared after the script is done executing. That’s why, from the Queues and Stateful Services article, statics can have an impact on the queue workers. The script never stops executing.
However, for normal use, each request will have its own isolated static variables. This is not like Java, where the statics will stick around until the application or the server is restarted.
Static Uses
The static life cycle is especially useful if there is information that needs to be passed around to multiple classes for the duration of the request. Optimal memory usage can be achieved by use of the singleton pattern or by lazy loading and locking statics.
Lazy Loading and Locking
I’m not sure what the official term for this is. It’s a combination of lazy loading, and locking a property-value.
class MemberList {
private static $names = [];
public static function getNames()
{
return (empty(self::$names))
? self::$names = app(NameRetriever::class)->getNames();
: self::$names;
}
}
In this example I am retrieving a list of names from the NameRetriever
which has been injected from the application container. However, I am only going to do this the first time it’s requested.
Any subsequent requests to the MemberList::getNames()
method will return the cached values inside of $names
. This can be especially useful if the operation for retrieving the list of names is overly complex.
Laravel actually uses a similar technique for serving singletons. They are stored in an array and their existence is checked before instantiation whenever an object is requested from the application container.
Singleton Pattern
Singletons are classes that are only instantiated once. Any subsequent request for the class will return the same instance. Laravel provides this functionality via app->singleton()
. However, this pattern can be applied even if you aren’t using a framework.
class ExampleSingleton {
private static $instance = null;
public static function create()
{
return (is_null(self::$instance))
? self::$instance = new self()
: self::$instance;
}
private function __construct() {
// constructor intentionally left private
}
}
$singletonOne = ExampleSingleton::create();
$singletonTwo = ExampleSingleton::create();
if ($singletonOne === $singletonTwo) {
echo "They are equal!";
} else {
echo "They are not!";
}
Running this script returns that they are equal.
This is very similar to the lazy loading and locking method. Only this time we are locking the $instance
so that it stores one instance of the class, and then returning it each time. We are also making the __construct
method private
to prevent bypassing the singleton pattern.
Top comments (0)