print_r() adds properties to DateTime objects [duplicate]
Asked Answered
D

7

19

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.

Distal answered 12/6, 2013 at 12:53 Comment(13)
@Jessica Run both of those blocks of code together. A warning is produced for the second, not for the first. The only difference is the print_r is commented out in the secondCeratoid
@Jessica the error occurs in the echo, not the print_rCrashaw
Doh. I was like, that seems so obvious. :-P I see what you mean, I ran the code - no clue.Lauraine
Oh wow. I just reproduced this. WOW. Seriously, PHP never ceases to amaze. Compare results across PHP versions.Meng
My guess is that this is some kind of internal private/protected property which is put into the object/made public when dumped.Estate
This is not a bug. Read the manual and you will see there is no public property 'date'.Passed
@Passed That's not what the question is about though; it's about why it appears after print_r(); granted, it probably resulted from a mistake in the first place :)Capitoline
@Jack Hence the comment and not another answer.Passed
@Meng Yeah, in 5.3 they introduced support for a more informative print_r() instead of just Object. I feel that was a mistake :)Capitoline
@Jack: Why do you say that? We all know that things happen in PHP only after rigorous scrutiny of both design and implementation, and the occasional bug is fixed promptly and cheerfully.Meng
@Meng I'm not sure whether the <sarcasm> tag was intentionally omitted ;-)Capitoline
I never thought my question goes so famous but yeah, I'm a little smarter now. Thx guys!Distal
@Jack: A differently named property - not "name" but "ROXXOR_IS_BACK!!" - is added when the some_func() function is called on DateTime. I guess, the reason why that is possible are the same as with the print_r function.Teach
G
9

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.

Geoffreygeoffry answered 12/6, 2013 at 13:3 Comment(1)
Thanks. That's the kind of response that answered my question most (In terms of my - original - symbolic question)Distal
G
14

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()
Glomeration answered 12/6, 2013 at 13:1 Comment(8)
That's no bug - it's a feature!Geoffreygeoffry
This is not a bug. Read the manual and you will see there is no public property 'date'.Passed
Its a bug. @Geoffreygeoffry explains it in his answer.Glomeration
@ClydeLobo Read the answer again. It isn't a bug, there is no public property called date, so being unable to access it is not a bug.Passed
@vascowhite: You have not understood the question.Meng
@Passed It's a bug because it should not magically become public after print_r() or var_dump(). Though, it could be reasoned that it's a "feature".Capitoline
I was only joking, but when something doesn't work, and it's not supposed to work in the first place, it's hard to justify calling it a 'bug'. The internals of a class are allowed to do what they want, it doesn't say anywhere that it won't be available.Geoffreygeoffry
Now you can use it as feature: $x = new DateTime(); ob_start(); print_r($x); ob_end_clean(); echo $x->date;Estate
C
10

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.

Comply answered 12/6, 2013 at 12:58 Comment(9)
Why the downvote? This is absolutely true. Although it doesn't explain why the error would not be seen when including the print_r()Trueblue
@Trueblue It doesn't answer the question.Ceratoid
print_r prints the object, echo needs a string to make sense.Comply
@DaveRandom: Not my dv, but even though it's true it totally misses the point of the question so it's quite bad when judged as an answer (the recent edit confirms this). We are not judging only the technical merit of an answer.Meng
@Meng I see where you are coming from but in a way it absolutely answers the question: The interface for DateTime as defined in the PHP manual, and therefore the canonical reference on the subject does not include a ->date property. So the question is effectively "why do I get an error when I try to access a property that's not part of the interface", and the answer to it when phrased that way is "because it's not part of the interface". I do understand the view that it doesn't exactly answer the question as asked, but I would counter than the OP asked the wrong question ;-)Trueblue
@DaveRandom: I disagree regarding the effective wording of the question. For me, it is "why do I not get an error if I interpose a call to print_r?". This answer does not touch that subject. Voting results seem to agree with this interpretation.Meng
@Trueblue not really - the question is why does print_r mask the warning in the first example. This post doesn't come close to addressing that, it's just a side commentCeratoid
@Meng I agree that is the real issue here (and it seems to have provoked a lively debate). I think this question is probably a keeper - although looking at the aforementioned debate it's sailing dangerously close to NC - but the question needs to be reworded to clarify that ^^ that is the salient point, and possibly have a [php-internals] tag added, feel free to bring the discussion to chat.stackoverflow.com/rooms/11/php if you have any thoughts on the matter.Trueblue
@DaveRandom: I agree on all of the above.Meng
G
9

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.

Geoffreygeoffry answered 12/6, 2013 at 13:3 Comment(1)
Thanks. That's the kind of response that answered my question most (In terms of my - original - symbolic question)Distal
E
5

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.

Estate answered 12/6, 2013 at 13:18 Comment(0)
T
2

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.

Teach answered 9/7, 2013 at 11:5 Comment(6)
And it also shows that this is totally easily possible with any object in PHP. Not really: codepad.viper-7.com/vwm2qpRedhead
@PLB: codepad.viper-7.com/vwm2qp - Not really as well. That site is down. But sure that sentence of mine was a bit of a mouthful.Teach
I meant objects that define __set method with custom implementation. E.g.: 3v4l.org/7EUhlRedhead
@PLB: Your example is incomplete ;). Even for those objects it is possible to set variable properties. See this code: 3v4l.org/itpSn .Teach
No, it was not incomplete. The morale behind that example was that objects might have such __get and/or __set implementation that public properties could not be added like that, which means that what you said is true for any object but these. These kind of implementations are really rare but they still exist.Redhead
@PLB: The only morale I can see here is that if you shield access to an object that you can not access it. However yours would require much more to be able to do so: 1. the class must be final. 2. you must prevent to unserialize the object. 3. you must protect that class from getting defined properties unset (yes it's getting esoteric). So I won't say this has any useful morale. Like with the variable properties I outline, you only show another common (here magic mehtods) that exists for all non-stdclass based objects in PHP. It's a wrong comparison for the educational purpose of my answer.Teach
V
1

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

Vasculum answered 12/6, 2013 at 12:53 Comment(0)
F
0

You should use DateTime::format or DateTimeImmutable::format as the case may be instead

$m_oDate = new DateTime('NOW');
echo $m_oDate->format("r");
Fruitarian answered 12/6, 2013 at 13:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.