Page 1 of 2
What is the best PHP equivalent of the "transient" keyword?
Posted: Wed Sep 28, 2011 12:45 pm
by freixas
The Question
PHP doesn't have a "transient" keyword. What is the best equivalent of "transient"? The answer should have the following characteristics:
- Performance: It should perform as well or better than serializing all variables.
- Encapsulation: It should not require any changes to ancestor or descendent classes.
Background
"Transient" is a keyword available in some computer languages, but not PHP. It is used as a modifier to a class property and tells the serialization function not to serialize that variable. Consider this code:
Code: Select all
<?php
class A {
private $cache = NULL;
public function myFunction() {
if ($cache === NULL) $cache = ...;
... do something ...
}
}
?>
If we serialize an instance of class A, the $cache variable will be serialized. This could add serious performance and storage penalties, depending on what $cache pointed to. If we add the "transient" modifier to $cache, we skip serializing it. When the serialized code is unserialized, $cache is back to being NULL and will be recalculated at first use.
Before you Answer...
With __sleep(), you can tell the serialization function which
variables to serialize. The problem is that any variable that is not
listed will not be serialized and you can't include any of the private
variables of any ancestor class.
Here's the best solution I came up with using the Serializable interface:
Code: Select all
class A implements Serializable
{
private $var1 = 565;
private $var3 = 555; // Don't serialize this variable
public function serialize()
{
$vars = get_object_vars($this);
unset($vars['var3']);
return serialize($vars);
}
public function unserialize($data)
{
$vars = unserialize($data);
foreach ($vars as $var => $value) {
$this->$var = $value;
}
}
}
class B extends A
{
private $var2 = 898;
private $var4 = 666; // Don't serialize this
public function serialize()
{
$vars = get_object_vars($this);
unset($vars['var4']);
return serialize(array(parent::serialize(), $vars));
}
public function unserialize($data)
{
$ar = unserialize($data);
parent::unserialize($ar[0]);
foreach ($ar[1] as $var => $value) {
$this->$var = $value;
}
}
}
With this implementation, all ancestor and descendent classes must also implement the serialization methods, parent items can be multiply serialized (which adds a few extra bytes) and the unserializing is probably slow.
A Language Enhancement?
I'm thinking of submit a enhancement request for adding "transient" to
PHP, but I thought I'd first see if anyone had a clever way to
implement this feature in PHP 5.3.
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Wed Sep 28, 2011 4:28 pm
by Christopher
I suspect that the first thing you need to do is stop thinking in Java and start thinking in PHP.

Second thing, why are you serializing objects?
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Wed Sep 28, 2011 5:00 pm
by freixas
Hi, Christopher,
Christopher wrote:I suspect that the first thing you need to do is stop thinking in Java and start thinking in PHP.
Exactly! That's what I want—the Official PHP Thinker's "transient" equivalent.
Christopher wrote:Second thing, why are you serializing objects?
Is this a bad thing or are you just wondering? In any case, I'm not the one serializing the object—the session handler is.
A discussion about whether objects should or should not be serialized is drifting

(Couldn't resist using that "smilie"). If you'd like to have that discussion, could you submit a new topic? Thanks!
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Wed Sep 28, 2011 6:15 pm
by Benjamin
Here's a patch. You can compile this and add it yourself. Obviously, your code won't be portable.
Code: Select all
Index: ext/json/json.c
===================================================================
--- ext/json/json.c (revision 289904)
+++ ext/json/json.c (working copy)
@@ -176,6 +176,7 @@
{
int i, r;
HashTable *myht;
+ zend_class_entry *ce = NULL;
if (Z_TYPE_PP(val) == IS_ARRAY) {
myht = HASH_OF(*val);
@@ -183,6 +184,10 @@
} else {
myht = Z_OBJPROP_PP(val);
r = PHP_JSON_OUTPUT_OBJECT;
+
+ if (Z_OBJ_HT_PP(val)->get_class_entry) {
+ ce = Z_OBJCE_PP(val);
+ }
}
if (myht && myht->nApplyCount > 1) {
@@ -215,6 +220,15 @@
if (i == HASH_KEY_NON_EXISTANT)
break;
+ if (ce && i == HASH_KEY_IS_STRING) {
+ zend_property_info *property_info;
+ if (zend_hash_find(&ce->properties_info, key, key_len, (void**) &property_info) == SUCCESS) {
+ if ((property_info->flags & ZEND_ACC_TRANSIENT) == ZEND_ACC_TRANSIENT) {
+ continue;
+ }
+ }
+ }
+
if (zend_hash_get_current_data_ex(myht, (void **) &data, &pos) == SUCCESS) {
tmp_ht = HASH_OF(*data);
if (tmp_ht) {
Index: ext/json/tests/008.phpt
===================================================================
--- ext/json/tests/008.phpt (revision 0)
+++ ext/json/tests/008.phpt (revision 0)
@@ -0,0 +1,22 @@
+--TEST--
+Test json_encode() with transient properties
+--FILE--
+<?php
+
+class members
+{
+ private $var_private = 10;
+ protected $var_protected = "string";
+ public $var_public = array(-100.123, "string", TRUE);
+ public transient $var_public_transient = "SECRET";
+}
+
+var_dump(json_encode(new members()));
+
+echo "\nDone";
+?>
+--EXPECTF--
+
+string(39) "{"var_public":[-100.123,"string",true]}"
+
+Done
Index: ext/standard/tests/serialize/serialization_objects_016.phpt
===================================================================
--- ext/standard/tests/serialize/serialization_objects_016.phpt (revision 0)
+++ ext/standard/tests/serialize/serialization_objects_016.phpt (revision 0)
@@ -0,0 +1,64 @@
+--TEST--
+Test serialize() & unserialize() functions: objects with transients
+--FILE--
+<?php
+
+echo "\n--- Testing objects ---\n";
+
+class members
+{
+ private $var_private = 10;
+ protected $var_protected = "string";
+ public $var_public = array(-100.123, "string", TRUE);
+ public transient $var_public_transient = "SECRET";
+}
+
+$members_obj = new members();
+var_dump( $members_obj );
+$serialize_data = serialize( $members_obj );
+var_dump( $serialize_data );
+$members_obj = unserialize( $serialize_data );
+var_dump( $members_obj );
+
+echo "\nDone";
+?>
+--EXPECTF--
+
+--- Testing objects ---
+object(members)#%d (4) {
+ ["var_private":"members":private]=>
+ int(10)
+ ["var_protected":protected]=>
+ string(6) "string"
+ ["var_public"]=>
+ array(3) {
+ [0]=>
+ float(-100.123)
+ [1]=>
+ string(6) "string"
+ [2]=>
+ bool(true)
+ }
+ ["var_public_transient"]=>
+ string(6) "SECRET"
+}
+string(195) "O:7:"members":3:{s:20:" members var_private";i:10;s:16:" * var_protected";s:6:"string";s:10:"var_public";a:3:{i:0;d:-100.1230000000000046611603465862572193145751953125;i:1;s:6:"string";i:2;b:1;}}"
+object(members)#%d (4) {
+ ["var_private":"members":private]=>
+ int(10)
+ ["var_protected":protected]=>
+ string(6) "string"
+ ["var_public"]=>
+ array(3) {
+ [0]=>
+ float(-100.123)
+ [1]=>
+ string(6) "string"
+ [2]=>
+ bool(true)
+ }
+ ["var_public_transient"]=>
+ string(6) "SECRET"
+}
+
+Done
Index: ext/standard/var.c
===================================================================
--- ext/standard/var.c (revision 289904)
+++ ext/standard/var.c (working copy)
@@ -741,44 +741,62 @@
}
case IS_ARRAY: {
zend_bool incomplete_class = 0;
+ zend_class_entry *ce = NULL;
+
if (Z_TYPE_P(struc) == IS_ARRAY) {
smart_str_appendl(buf, "a:", 2);
myht = HASH_OF(struc);
} else {
incomplete_class = php_var_serialize_class_name(buf, struc TSRMLS_CC);
myht = Z_OBJPROP_P(struc);
+
+ if (Z_OBJ_HT_P(struc)->get_class_entry) {
+ ce = Z_OBJCE_P(struc);
+ }
}
+
/* count after serializing name, since php_var_serialize_class_name
* changes the count if the variable is incomplete class */
i = myht ? zend_hash_num_elements(myht) : 0;
- if (i > 0 && incomplete_class) {
- --i;
- }
- smart_str_append_long(buf, i);
- smart_str_appendl(buf, ":{", 2);
+
if (i > 0) {
char *key;
zval **data;
ulong index;
uint key_len;
+ int key_type = 0;
HashPosition pos;
+ smart_str array_buf = {0};
zend_hash_internal_pointer_reset_ex(myht, &pos);
for (;; zend_hash_move_forward_ex(myht, &pos)) {
- i = zend_hash_get_current_key_ex(myht, &key, &key_len, &index, 0, &pos);
- if (i == HASH_KEY_NON_EXISTANT) {
- break;
+ key_type = zend_hash_get_current_key_ex(myht, &key, &key_len, &index, 0, &pos);
+
+ if (key_type == HASH_KEY_NON_EXISTANT) {
+ goto finish_encoding;
}
+
if (incomplete_class && strcmp(key, MAGIC_MEMBER) == 0) {
+ --i;
continue;
}
- switch (i) {
+ if (ce && key_type == HASH_KEY_IS_STRING) {
+ zend_property_info *property_info;
+ if (zend_hash_find(&ce->properties_info, key, key_len, (void**) &property_info) == SUCCESS) {
+ if ((property_info->flags & ZEND_ACC_TRANSIENT) == ZEND_ACC_TRANSIENT) {
+ --i;
+ continue;
+ }
+ }
+ }
+
+ switch (key_type) {
case HASH_KEY_IS_LONG:
- php_var_serialize_long(buf, index);
+ php_var_serialize_long(&array_buf, index);
break;
case HASH_KEY_IS_STRING:
- php_var_serialize_string(buf, key, key_len - 1);
+ php_var_serialize_string(&array_buf, key, key_len - 1);
break;
}
@@ -789,18 +807,27 @@
|| data == &struc
|| (Z_TYPE_PP(data) == IS_ARRAY && Z_ARRVAL_PP(data)->nApplyCount > 1)
) {
- smart_str_appendl(buf, "N;", 2);
+ smart_str_appendl(&array_buf, "N;", 2);
} else {
if (Z_TYPE_PP(data) == IS_ARRAY) {
Z_ARRVAL_PP(data)->nApplyCount++;
}
- php_var_serialize_intern(buf, *data, var_hash TSRMLS_CC);
+ php_var_serialize_intern(&array_buf, *data, var_hash TSRMLS_CC);
if (Z_TYPE_PP(data) == IS_ARRAY) {
Z_ARRVAL_PP(data)->nApplyCount--;
}
}
}
+finish_encoding:
+ smart_str_append_long(buf, i);
+ smart_str_appendl(buf, ":{", 2);
+ smart_str_appendl(buf, array_buf.c, array_buf.len);
+ smart_str_free(&array_buf);
+ } else {
+ smart_str_append_long(buf, i);
+ smart_str_appendl(buf, ":{", 2);
}
+
smart_str_appendc(buf, '}');
return;
}
Index: Zend/tests/access_modifiers_013.phpt
===================================================================
--- Zend/tests/access_modifiers_013.phpt (revision 0)
+++ Zend/tests/access_modifiers_013.phpt (revision 0)
@@ -0,0 +1,13 @@
+--TEST--
+Transient properties
+--FILE--
+<?php
+class Hobo {
+ public transient $moveAlongNow = 'Spare some change?';
+}
+
+echo "Done\n";
+
+?>
+--EXPECTF--
+Done
Index: Zend/zend_language_scanner.l
===================================================================
--- Zend/zend_language_scanner.l (revision 289904)
+++ Zend/zend_language_scanner.l (working copy)
@@ -1142,6 +1142,10 @@
return T_PUBLIC;
}
+<ST_IN_SCRIPTING>"transient" {
+ return T_TRANSIENT;
+}
+
<ST_IN_SCRIPTING>"unset" {
return T_UNSET;
}
Index: Zend/zend_language_parser.y
===================================================================
--- Zend/zend_language_parser.y (revision 289904)
+++ Zend/zend_language_parser.y (working copy)
@@ -114,7 +114,7 @@
%token T_THROW
%token T_USE
%token T_GLOBAL
-%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC
+%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_TRANSIENT
%token T_VAR
%token T_UNSET
%token T_ISSET
@@ -546,6 +546,7 @@
| T_STATIC { Z_LVAL($$.u.constant) = ZEND_ACC_STATIC; }
| T_ABSTRACT { Z_LVAL($$.u.constant) = ZEND_ACC_ABSTRACT; }
| T_FINAL { Z_LVAL($$.u.constant) = ZEND_ACC_FINAL; }
+ | T_TRANSIENT { Z_LVAL($$.u.constant) = ZEND_ACC_TRANSIENT; }
;
class_variable_declaration:
Index: Zend/zend_compile.h
===================================================================
--- Zend/zend_compile.h (revision 289904)
+++ Zend/zend_compile.h (working copy)
@@ -112,6 +112,7 @@
#define ZEND_ACC_ABSTRACT 0x02
#define ZEND_ACC_FINAL 0x04
#define ZEND_ACC_IMPLEMENTED_ABSTRACT 0x08
+#define ZEND_ACC_TRANSIENT 0x10
/* class flags (types) */
/* ZEND_ACC_IMPLICIT_ABSTRACT_CLASS is used for abstract classes (since it is set by any abstract method even interfaces MAY have it set, too). */
A discussion about whether the PHP Core should or should not be customized is drifting

(Couldn't resist using that "smilie"). If you'd like to have that discussion, could you submit a new topic? Thanks!
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Wed Sep 28, 2011 6:35 pm
by freixas
@Benjamin: Actually, I confess I failed to search the php.net bug list to see if transient was discussed. I see that there is an enhancement request at
https://bugs.php.net/bug.php?id=39635 which points to the patch you just posted. This was submitted in 2006 and the last comment was from 2009. I guess I'll add my vote to the bug, FWIW.
Since it seems unlikely that the transient keyword will be added anytime in the near future, an answer to my original question is even more interesting. Can anyone do better than what I proposed?
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Wed Sep 28, 2011 7:21 pm
by Christopher
Seems like it might be easier to use __sleep() and __wakeup() instead of Serializable.
My question was not whether you should serialize or not -- it was why, which you answered. My next question is why are you using private? It is causing some of the difficulties you are having. Why are you protecting those properties from children? Private is popular in other languages, not so in PHP.
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Wed Sep 28, 2011 7:48 pm
by freixas
@Christopher: __sleep and __wakeup won't work unless all your variables are public, all the way up and down the class hierarchy.
Why are you protecting those properties from children? Private is popular in other languages, not so in PHP.
One could maybe say that object oriented programming is not as popular in PHP as it is in other languages, but I don't see that as particularly relevant. Why use private variables in PHP? Same reason as for using them anywhere else (plenty of articles on this on the web, I'm sure).
I don't mean to discourage you, but the question I am asking is about the closest way to implement a PHP equivalent of "transient". I'm not interested in solutions to other problems (such as: how do I avoid serializing a variable when all object properties are public?—I know how to do that).
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Thu Sep 29, 2011 12:55 am
by Christopher
freixas wrote:I'm not interested in solutions to other problems
Implying that all problems have the same solution in every language.
freixas wrote:(such as: how do I avoid serializing a variable when all object properties are public?—I know how to do that).
That's why there is protected. Just wondering what private gives you that protected doesn't. I do understand why private is use in long-running programs.
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Thu Sep 29, 2011 6:40 am
by freixas
Christopher wrote:freixas wrote:I'm not interested in solutions to other problems
Implying that all problems have the same solution in every language.
No, the only thing it implies is that, for the purposes of this post, I'm only interested in an answer to the question I posted.
freixas wrote:(such as: how do I avoid serializing a variable when all object properties are public?—I know how to do that).
That's why there is protected. Just wondering what private gives you that protected doesn't. I do understand why private is use in long-running programs.
The problems with protected are exactly the same as with public. Again, this leads us

Feel free to send me a message if you need help understanding why.
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Thu Sep 29, 2011 7:27 am
by Benjamin
freixas wrote:The problems with protected are exactly the same as with public. Again, this leads us

Feel free to send me a message if you need help understanding why.
freixas, as much as I hate to say this, it doesn't matter if you are the best programmer in the world at whatever language you are familiar with.
You are on a PHP forum, asking questions about PHP. You would not be here if you didn't have questions.
We are a tight knight group here. We all know each other and we've all been helping each other out for years. We like to discuss things. We try to stay on topic, but invariably end up discussing related issues.
We would love to give you advice to your questions and help you out, but you've given us no context. Without context, we can't recommend any solutions.
You want to do something that perhaps isn't possible in PHP, but with the proper context we might be able to offer a better solution. PHP is not inferior to whatever language you are used to. It may not have X feature, but it's likely a hell of a lot more flexible.
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Thu Sep 29, 2011 8:50 am
by freixas
@Benjamin: I don't mind discussing things, I'd just like to keep this topic on topic. We can discuss other things in a new topic or by email.
As for context, the context is finding the closest PHP equivalent of "transient". Transient is used to avoid serializing an object member. Why would you want to do this?
- The variable represents a resource or an object that can't be serialized.
- The variable doesn't need to be serialized (it's a temporary or cached value)
- The variable represents some state that might not be appropriate when the variable is unserialized
- The variable points to something that pulls in a ton of unneeded stuff (think of debug_backtrace()).
Of course, in all these cases, the unserialized object needs to be able to re-create or live with a default value for the excluded variable.
I have a class library (think of something like PHP's SPL) where some classes have a deep inheritance chain (in PHP's SPL, check out CachingRecursiveIterator for a similar chain). I want to find ways of implementing "transient" that perform well and minimize the implementation burden of anyone using the library. I can't predict how, why or when someone might decide to serialize an instance of a library class (or their own subclass thereof)—I just want it to work correctly, efficiently and with as little fuss as possible.
I have no idea why you imply that I think PHP is inferior to some other language. PHP is the main language I work in these days, both for web work and for CLI scripts and has been for about the last six years.
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Thu Sep 29, 2011 9:49 am
by Benjamin
Ok.
So there's a few different ways to look at this. You'll have to forgive my ignorance as I've not dealt with the serialization of objects on a routine basis. I'm not going to run any code here, or test any of these theories. I'm only going to brainstorm.
Here's what needs to be done:
- We have objects we need to serialize.
- The objects contain items we may not want to serialize.
- We want to be able to have some sort of capability for this to be done automatically, without having to specify every item that should not be serialized.
Let's look at the manual carefully and see what it says:
The value to be serialized. serialize() handles all types, except the resource-type. You can even serialize() arrays that contain references to itself. Circular references inside the array/object you are serializing will also be stored. Any other reference will be lost.
When serializing objects, PHP will attempt to call the member function __sleep prior to serialization. This is to allow the object to do any last minute clean-up, etc. prior to being serialized. Likewise, when the object is restored using unserialize() the __wakeup member function is called.
There are four things that stand out here.
- We know resource types cannot be serialized.
- Any other reference will be lost? Not sure what that means, but maybe there's something there.
- We know we can intercept the serialize process coming and going with the sleep and wakeup methods.
You mentioned that the classes have a deep inheritance chain. We know that the __sleep and __wakeup methods of the base classes will be called when the object is serialized.
Without testing this, I do not know if the base class is able to loop through the variables in the parent class/class chain. But through some clever programming, maybe it would be possible to iterate through these and detect variables with a certain naming convention and unset them.
Further, if this is possible, maybe it would be better to use the __wakeup method to detect them and confirm that they should even be unset to improve performance.
Hope that helps.
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Thu Sep 29, 2011 11:15 am
by freixas
@Benjamin: Hi, this is exactly the kind of response I was looking for. Brainstorming is fine.
Here's the problem (I discovered this by trying it): PHP works up the class hierarchy until it encounters a definition of __sleep() (this is just standard inheritance). This _sleep() method cannot return any private value for a parent. It doesn't matter if you chain up the class hierarchy—that was I thought of trying, too. You can gather up all the private (and other) variables this way, but you can't include them in the array you return from __sleep(). If you do, you get an error.
You could try using reflection or whatever, it doesn't matter. The __sleep() method is deeply flawed—once you use it for any reason, you lose the ability to serialize any private variables in parent classes. It's only useful for serializing objects that have no ancestors or descendents. This is the reason that the Serializable interface was introduced and why my proposal chains using Serializable instead of __sleep().
Again, brainstorming is fine, even if it turns out that it's something I already tried. Also, in explaining why your approach won't work, perhaps you'll note a problem in my reasoning or some other variant that overcomes the problems listed.
Thanks!
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Fri Sep 30, 2011 1:13 am
by Benjamin
Offhand the only other way I can think of to do this would be to write a class that cannot be serialized, then any variable that you do not want serialized would need to be an instance of that class.
Re: What is the best PHP equivalent of the "transient" keywo
Posted: Fri Sep 30, 2011 6:39 pm
by freixas
I took "write a class that cannot be serialized" to mean "write a wrapper class that prevents a value from being serialized". Here's the implementation
Code: Select all
<?php
class SerializationProofWrapper
{
public $value;
public function __construct($value) { $this->value = $value; }
public function __sleep() { return array(); }
}
class A {
private $var1;
private $var2;
public function __construct()
{
$this->var1 = 100;
$this->var2 = new SerializationProofWrapper(200);
}
}
class B extends A {
private $var3;
private $var4;
public function __construct()
{
$this->var3 = 300;
$this->var4 = 400;
parent::__construct();
}
}
$b = new B();
var_dump($b);
var_dump(unserialize(serialize($b)));
Here's the output:
[text]object(B)#1 (4) {
["var3":"B":private]=>
int(300)
["var4":"B":private]=>
int(400)
["var1":"A":private]=>
int(100)
["var2":"A":private]=>
object(SerializationProofWrapper)#2 (1) {
["value":"SerializationProofWrapper":private]=>
int(200)
}
}
object(B)#3 (4) {
["var3":"B":private]=>
int(300)
["var4":"B":private]=>
int(400)
["var1":"A":private]=>
int(100)
["var2":"A":private]=>
object(SerializationProofWrapper)#4 (1) {
["value":"SerializationProofWrapper":private]=>
NULL
}
}[/text]
It works and the subclass doesn't have to do a thing. There is a tiny overhead from having to parse the wrapper and from the indirect access to the wrapped value. The use of __sleep() also slows things down a bit, but any solution would involve either _sleep() or implementing the Serializable interface. Short of having "transient" added to the language, this seems pretty close to optimal.
Thanks!
