PHP serialize protected class variables with wrong character count? - php

I am dealing with a strange problem in PHP 5.2.6, serializing objects.
Apparently, PHP stores protected variables members with an asterix ahead of its name. That is normal and correct, however, look at the following example:
O:18:"object__songChords":1:{s:9:"*chords"}
*chords does not contain 9 characters, but 7 (with the asterix).
Naturally, i assumed that there is some misunderstanding on my side, and that PHP just counts the quotes, but when i compare this to other strings stored, i find that this is not the case.
Bottomline is that PHP fails to unserialize these objects, although the classes have not changed.
Notice: unserialize(): Error at offset 43 of 867 bytes in ...
Can anyone offer some insight on this?
EDIT (providing the class)
class object__songChords {
protected $chords;
protected $lyrics;
}
Please note that i stripped the above serialization example for the sake of simplicity – the actual serialization string of course contains both properties.

The count is not off, there are two \0 null characters separating the *, one on each side:
$o = new object__songChords;
echo addslashes(serialize($o));
Yields:
O:18:\"object__songChords\":2:{s:9:\"\0*\0chords\";N;s:9:\"\0*\0lyrics\";N;}

Related

Warnings: Array to string conversion, undefined property in PHP 8.1 trying to call a property of an object (worked in PHP 5.5) [duplicate]

This question already has answers here:
PHP Notice: Array to string conversion only on PHP 7
(2 answers)
Array to string conversion - Variable function name calling
(1 answer)
Closed 11 months ago.
[NOTE: I solved this problem in the course of drafting the
question, but figured I would proceed with posting it for future
reference, since my extensive searching on the warnings delivered did
not provide the solution and I figure this might help someone in the
future.]
I have a class Thing that has properties $subthing_1, $subthing_2, $subthing_3, all of which are objects:
class Thing {
...
public $subthing_1; // object of type Subthing
public $subthing_2; // object of type Subthing
public $subthing_3; // object of type Subthing
...
}
If I want to get a particular subthing in an instance of Thing, I can do:
$thing->subthing_1
and this works just fine to deliver that subthing. But I want to generalize which of the subthings are called, so I have a routine that gets the name of a particular subthing, either 'subthing_1', 'subthing_2', or 'subthing_3', and stores it in a string in an array element. Thus, $subthings[4] has string value 'subthing_1', for example, and
$thing->$subthings[4]
should deliver exactly the same as $thing->subthing_1.
If I do
var_dump($subthings[4]);
the result is
string(10) "subthing_1"
and for
var_dump($thing);
the result is
object(Thing)#4 (87) {
...
["subthing_1"]=>
object(Category)#59 (12) {
...
}
...
}
Which is all fine and good. However, when I try to do this:
var_dump($thing->$subthings[4]);
I get this:
Warning: Array to string conversion ...
Warning: Undefined property: Thing::$Array ...
Warning: Trying to access array offset on value of type null ...
This confused me for a long time, because I couldn't see where an array was being converted to a string. $subthings[4] is clearly a string, as indicated by var_dump($subthings[4]), not an array.
These warnings are appearing in PHP 8.1 for code that worked just fine in PHP 5.5.
Eventually I reasoned that there must have been a syntax interpretation change somewhere between these two PHP versions.
Whereas in PHP 5.5, $thing->$subthings[4] is interpreted as $thing->{$subthings[4]}, in PHP 8.1 $thing->$subthings[4] is interpreted as {$thing->$subthings}[4].
I have searched high and low for documentation of this change, but I guess I am not hitting on the right keywords. I hope someone here can supply an answer for where this change is documented.
The SOLUTION is to surround the arrays with curly braces to enforce the expected (and formerly default) parse ordering:
$thing->{$subthings[4]}
As pointed out by Don't Panic, this evaluation syntax change is documented here:
https://www.php.net/manual/en/migration70.incompatible.php#migration70.incompatible.variable-handling.indirect

PHP array object unexplained behavor [duplicate]

This question already has answers here:
Why can't I access DateTime->date in PHP's DateTime class?
(5 answers)
Closed 5 years ago.
Consider the following code sample:
$m_oDate = new DateTime('2013-06-12 15:54:25');
print_r($m_oDate);
echo $m_oDate->date;
Since PHP 5.3, this produces (something like) the following output:
DateTime Object
(
[date] => 2013-06-12 15:54:25
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
2013-06-12 15:54:25
However the following code:
$m_oDate = new DateTime('2013-06-12 15:54:25');
echo $m_oDate->date;
...simply emits an error:
Notice: Undefined property: DateTime::$date in ...
Why does print_r() "add" these properties to the object? Note that they are not defined as part of the DateTime class on the manual page.
This has been reported as Bug #49382 in PHP.
In PHP 5.3, internal functionality was added to allow print_r() to show details of the underlying timestamp value held by an instance of DateTime, to assist with debugging. A side effect of this change is that when the object is dumped to text, these phantom public properties are added to the instance.
The same effect can be achieved by using reflection to access these properties, and if you need to access the properties then using reflection would be the way to go, so you don't provoke the error.
However, it should be noted that you shouldn't really use these properties - since they are not defined as members of the object, there is no guarantee they will continue to carry the same data (or even exist) in future PHP versions. If you need to access the information, use the following methods, defined as part of the API, instead:
// $obj->date
$obj->format('Y-m-d H:i:s');
// $obj->timezone
$obj->getTimezone()->getName();
// or...
$obj->getTimezone()->getOffset();
// or...
$obj->getTimezone()->listAbbreviations(); // returns an array, so may need
// further processing to be of use
NB: The timezone_type property is not accessible through the PHP API. It is an internal value and not useful in userland, because it describes the type of string that timezone holds when the object is dumped - i.e. one of the three methods for obtaining timezone information in the code sample above. For completeness, its possible values are defined in the following way:
Value | Type | Userland equivalent
------+-----------------------+----------------------------------
1 | time offset | DateTimeZone::getOffset()
2 | TimeZone abbreviation | DateTimeZone::listAbbreviations()
3 | TimeZone identifier | DateTimeZone::getName()
There is some magic occurring but it's pretty simple.
The class DateTime doesn't have a public variable 'date' that you're meant to access. However, as a side effect of how PHP works, there is a variable created when you call print_r or var_dump on that class.
After that magic happens 'date' is available, but it shouldn't be. You should just use the getTimestamp function to make your code work reliably.
There's no date property in DateTime; that's why you're getting (Undefined property: DateTime::$date).
print_r() performs some introspection on the object to display its contents; this causes the object to magically create the ::date property. This is not documented though, so using this may break your code in the future.
You need something like $m_oDate->format('m-d-Y'); instead.
The problem lies here:
static HashTable *date_object_get_properties(zval *object TSRMLS_DC)
{
// ...
zend_hash_update(props, "date", 5, &zv, sizeof(zval), NULL);
// ...
The function date_object_get_properties is called when any data dumping is made (print_r, var_dump, var_export). The hash table is updated for data representing, unfortunately this is made public.
Consider the following code-example:
$m_oDate = new DateTime('2013-06-12 15:54:25');
some_func($m_oDate);
echo $m_oDate->{'ROXXOR_IS_BACK!!'};
The most obvious difference to yours is that instead of the print_r function a different one some_func is called. The expectations might differ because you know print_r but you don't know some_func but that is only to sharpen your senses. Let's wait a moment until I show the definition of that function.
The second difference is the name of the property that is getting echoed. Here I've choosen a name that is really exceptional: {'ROXXOR_IS_BACK!!'}, again to sharpen your senses.
This name is that crazy that is must be obvious not being part of DateTime albeit when the example above is executed, it's totally clear that this roxxor property must exists. Program Output:
PHP never lets you down.
So how come? Yes, you sure already have an idea how that works. The some_func() function must have added it. So let's take a look into the functions definition:
function some_func($m_oDate) {
$m_oDate->{'ROXXOR_IS_BACK!!'} = 'PHP never lets you down.';
}
Yes, it's now clearly visible that this function has added a property to the object. And it also shows that this is totally easily possible with any object in PHP.
Compare with an array in PHP, you can also add new keys when you want to.
This example has not been chosen out of nothing, because this is where objects in PHP come from: They are just syntactic sugar around arrays and this has to with the time when objects in PHP were introduced back in PHP 3:
At the time classes were introduced to the source tree of what was to become PHP 3.0, they were added as syntactic sugar for accessing collections. PHP already had the notion of associative array collections, and the new critters were nothing but a neat new way of accessing them. However, as time has shown, this new syntax proved to have a much more far-reaching effect on PHP than was originally intended.
-- Zeev Suraski about the standard object since PHP 3 (archived copy) - via Why return object instead of array?
This is also the simple explanation why it's totally common in PHP that functions can add member variables which have not been defined earlier in the class. Those are always public.
So when you do assumptions about some object having a property or not, take a look where it comes from. It must not be part of the class but might have been added later.
And keep the following in mind:
Do not use print_r and var_dump in production code.
For the fun of it, this is how you can make it work, using Reflection:
$m_oDate = new DateTime('NOW');
$o = new ReflectionObject($m_oDate);
$p = $o->getProperty('date');
echo $p->getValue($m_oDate);
Source
You should use DateTime::format or DateTimeImmutable::format as the case may be instead
$m_oDate = new DateTime('NOW');
echo $m_oDate->format("r");

What are these undocumented properties in DateInterval? [duplicate]

This question already has answers here:
Why can't I access DateTime->date in PHP's DateTime class?
(5 answers)
Closed 5 years ago.
Consider the following code sample:
$m_oDate = new DateTime('2013-06-12 15:54:25');
print_r($m_oDate);
echo $m_oDate->date;
Since PHP 5.3, this produces (something like) the following output:
DateTime Object
(
[date] => 2013-06-12 15:54:25
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
2013-06-12 15:54:25
However the following code:
$m_oDate = new DateTime('2013-06-12 15:54:25');
echo $m_oDate->date;
...simply emits an error:
Notice: Undefined property: DateTime::$date in ...
Why does print_r() "add" these properties to the object? Note that they are not defined as part of the DateTime class on the manual page.
This has been reported as Bug #49382 in PHP.
In PHP 5.3, internal functionality was added to allow print_r() to show details of the underlying timestamp value held by an instance of DateTime, to assist with debugging. A side effect of this change is that when the object is dumped to text, these phantom public properties are added to the instance.
The same effect can be achieved by using reflection to access these properties, and if you need to access the properties then using reflection would be the way to go, so you don't provoke the error.
However, it should be noted that you shouldn't really use these properties - since they are not defined as members of the object, there is no guarantee they will continue to carry the same data (or even exist) in future PHP versions. If you need to access the information, use the following methods, defined as part of the API, instead:
// $obj->date
$obj->format('Y-m-d H:i:s');
// $obj->timezone
$obj->getTimezone()->getName();
// or...
$obj->getTimezone()->getOffset();
// or...
$obj->getTimezone()->listAbbreviations(); // returns an array, so may need
// further processing to be of use
NB: The timezone_type property is not accessible through the PHP API. It is an internal value and not useful in userland, because it describes the type of string that timezone holds when the object is dumped - i.e. one of the three methods for obtaining timezone information in the code sample above. For completeness, its possible values are defined in the following way:
Value | Type | Userland equivalent
------+-----------------------+----------------------------------
1 | time offset | DateTimeZone::getOffset()
2 | TimeZone abbreviation | DateTimeZone::listAbbreviations()
3 | TimeZone identifier | DateTimeZone::getName()
There is some magic occurring but it's pretty simple.
The class DateTime doesn't have a public variable 'date' that you're meant to access. However, as a side effect of how PHP works, there is a variable created when you call print_r or var_dump on that class.
After that magic happens 'date' is available, but it shouldn't be. You should just use the getTimestamp function to make your code work reliably.
There's no date property in DateTime; that's why you're getting (Undefined property: DateTime::$date).
print_r() performs some introspection on the object to display its contents; this causes the object to magically create the ::date property. This is not documented though, so using this may break your code in the future.
You need something like $m_oDate->format('m-d-Y'); instead.
The problem lies here:
static HashTable *date_object_get_properties(zval *object TSRMLS_DC)
{
// ...
zend_hash_update(props, "date", 5, &zv, sizeof(zval), NULL);
// ...
The function date_object_get_properties is called when any data dumping is made (print_r, var_dump, var_export). The hash table is updated for data representing, unfortunately this is made public.
Consider the following code-example:
$m_oDate = new DateTime('2013-06-12 15:54:25');
some_func($m_oDate);
echo $m_oDate->{'ROXXOR_IS_BACK!!'};
The most obvious difference to yours is that instead of the print_r function a different one some_func is called. The expectations might differ because you know print_r but you don't know some_func but that is only to sharpen your senses. Let's wait a moment until I show the definition of that function.
The second difference is the name of the property that is getting echoed. Here I've choosen a name that is really exceptional: {'ROXXOR_IS_BACK!!'}, again to sharpen your senses.
This name is that crazy that is must be obvious not being part of DateTime albeit when the example above is executed, it's totally clear that this roxxor property must exists. Program Output:
PHP never lets you down.
So how come? Yes, you sure already have an idea how that works. The some_func() function must have added it. So let's take a look into the functions definition:
function some_func($m_oDate) {
$m_oDate->{'ROXXOR_IS_BACK!!'} = 'PHP never lets you down.';
}
Yes, it's now clearly visible that this function has added a property to the object. And it also shows that this is totally easily possible with any object in PHP.
Compare with an array in PHP, you can also add new keys when you want to.
This example has not been chosen out of nothing, because this is where objects in PHP come from: They are just syntactic sugar around arrays and this has to with the time when objects in PHP were introduced back in PHP 3:
At the time classes were introduced to the source tree of what was to become PHP 3.0, they were added as syntactic sugar for accessing collections. PHP already had the notion of associative array collections, and the new critters were nothing but a neat new way of accessing them. However, as time has shown, this new syntax proved to have a much more far-reaching effect on PHP than was originally intended.
-- Zeev Suraski about the standard object since PHP 3 (archived copy) - via Why return object instead of array?
This is also the simple explanation why it's totally common in PHP that functions can add member variables which have not been defined earlier in the class. Those are always public.
So when you do assumptions about some object having a property or not, take a look where it comes from. It must not be part of the class but might have been added later.
And keep the following in mind:
Do not use print_r and var_dump in production code.
For the fun of it, this is how you can make it work, using Reflection:
$m_oDate = new DateTime('NOW');
$o = new ReflectionObject($m_oDate);
$p = $o->getProperty('date');
echo $p->getValue($m_oDate);
Source
You should use DateTime::format or DateTimeImmutable::format as the case may be instead
$m_oDate = new DateTime('NOW');
echo $m_oDate->format("r");

PHP function passing arguuments?

Now I am dealing with PHP but I`ve stucked with a strange issue:
I am making a simple foo for string length:
function slen($str) {
$len;
for ( $len=0; $str[$len]; $len++ ) {
/* while tehre is a symbol in $str continue counting, old C metnod */
}
return (int)$len;
}
it`s on as a code, and outputs the length but it booms with an PHP warning message :
PHP Notice: Uninitialized string offset: 3 in /home/ilian/Desktop/SERVER/ex4.php on line 5
And on my line 5 is the for loop initialization. So I got that PHP might be confusing by not knowing what kind of variables it checks for TRUE in that array because it can be
"Array", "HELLO", "MESS", 50 and length is 4 which is correct, but in the case I aam checking a simple string for length. Any easygoing explanation?
The problem is that at some point your condition ($str[$len]) will try to access an offset beyond the end of the string. That's your stopping condition, but that stopping condition throws the notice, since accessing an undefined offset is considered an error (and rightly so). You'll need to check whether the offset exists using isset($str[$len]), which does not throw a notice.
You don't have to write a function to get the length of a string, PHP has this built in for you in several internal functions:
strlen - Length of a string
mb_strlen - Length of a string which may have multi-byte characters within it
count - Length of an array or count of items in an object
If all you need is to count how long a string is, go with strlen, for arrays you'd use count. Much better than rolling your own.

PHP 5.4's simplified string offset reading

As many of you already know, PHP 5.4 alpha has been released. I have a question regarding the following.
Simplified string offset reading.
$str[1][0] is now a legal construct.
How exactly does $str[1][0] work?
EDIT: http://php.net/releases/NEWS_5_4_0_alpha1.txt
It just means that when reading a string offset PHP returns a string again, on which you again can access an offset. (And on that access yet another offset. It gets funny with $str[0][0][0][0][0][0])
Before PHP 5.4 you would get an "Cannot use string offset as an array" error.
This is a side effect, and was mentioned in the proposal here:
http://php.markmail.org/thread/yiujwve6zdw37tpv
The feature is speed/optimization of string offsets.
Hi,
Recently I noticed that reading of
string offset is performed in two
steps. At first special string_offset
variant of temporary_variable is
created in
zend_fetch_dimension_address_read()
and then the real string value is
created in
_get_zval_ptr_var_string_offset().
I think we can create the real string
in the first place. This makes 50%
speed-up on string offset reading
operation and allows to eliminate some
checks and conditional brunches in VM.
The patch is attached (don't forget to
regenerate zend_vm_execute.h to test
it). However it changes behavior in
one bogus case. The following code now
will emit "b" (currently it generates
a fatal error - cannot use string
offset as an array).
$str = "abs";
var_dump($str[1][0]);
I think it's not a problem at all. "b"
makes sense because "abs"[1] -> "b"
and "b"[0] -> "b".
I'm going to commit the patch in case
of no objections.
Thanks. Dmitry.
This can actually create some interesting bugs when you upgrade code from php 5.3 to 5.4.
In 5.3 this construct would return false:
$array = array("This is a string");
echo isset($array[0][0][0]);
In 5.4 this would return true.

Categories