Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

var_export() combined with enum produces code unsuitable for inclusion in namespaces #8232

Closed
Ocramius opened this issue Mar 21, 2022 · 9 comments

Comments

@Ocramius
Copy link
Contributor

Ocramius commented Mar 21, 2022

Description

Following code: https://3v4l.org/MKPpJ

<?php

namespace A;

enum Foo { case BAR; }

$bar = var_export(Foo::BAR, true);

echo <<<PHP
<?php

namespace Generated\Code;

echo $bar;
PHP;

Resulted in this output:

<?php

namespace Generated\Code;

echo A\Foo::BAR;

But I expected this output instead:

<?php

namespace Generated\Code;

echo \A\Foo::BAR;

To be more specific:

<?php

namespace Generated\Code;

-echo A\Foo::BAR;
+echo \A\Foo::BAR;

This allows for generated PHP expressions (primary use-case of var_export()) to contain enums.

Ref: Ocramius/ProxyManager#754 (comment)

PHP Version

8.1.4

Operating System

No response

@Ocramius
Copy link
Contributor Author

Seems like this applies to both object and enum definitions - much older bug, exposed by the fact that property default values can now contain constants pointing to object references.

Ocramius added a commit to Ocramius/php-src that referenced this issue Mar 21, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

 * properties with object initializers
 * constants containing object references
 * default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
Ocramius added a commit to Ocramius/php-src that referenced this issue Mar 21, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

 * properties with object initializers
 * constants containing object references
 * default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
Ocramius added a commit to Ocramius/php-src that referenced this issue Mar 21, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

 * properties with object initializers
 * constants containing object references
 * default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
Ocramius added a commit to Ocramius/php-src that referenced this issue Mar 21, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

* properties with object initializers
* constants containing object references
* default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
Ocramius added a commit to Ocramius/php-src that referenced this issue Mar 21, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

* properties with object initializers
* constants containing object references
* default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
Ocramius added a commit to Ocramius/doc-en that referenced this issue Mar 21, 2022
…ort()` docs

`var_export()` needs to prefix classes it references with `\`, because its code could be transplanted in
any source location/namespace, so the assumption of it being used only in the context of the root
namespace is not sufficient.

For example, in a code snippet like following ( https://3v4l.org/4mONc ):

```php
<?php

class SomeObject {}
var_export([new SomeObject]);
```

This should produce:

```
array (
  0 => 
  \SomeObject::__set_state(array(
  )),
)
```

Userland should not concern itself with the contents of the `var_export()`-produced code
snippets, and use them as-is instead.

Ref: php/php-src#8232
Ref: php/php-src#8233
Ref: Ocramius/ProxyManager#754
Ocramius added a commit to Ocramius/php-src that referenced this issue Mar 21, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

* properties with object initializers
* constants containing object references
* default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
Ocramius added a commit to Ocramius/php-src that referenced this issue Mar 21, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

* properties with object initializers
* constants containing object references
* default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
Ocramius added a commit to Ocramius/php-src that referenced this issue Mar 21, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

* properties with object initializers
* constants containing object references
* default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
@iluuu1994 iluuu1994 self-assigned this Mar 31, 2022
iluuu1994 pushed a commit to Ocramius/php-src that referenced this issue Apr 22, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

* properties with object initializers
* constants containing object references
* default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
iluuu1994 pushed a commit to Ocramius/php-src that referenced this issue Apr 22, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

* properties with object initializers
* constants containing object references
* default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
iluuu1994 pushed a commit to Ocramius/php-src that referenced this issue Apr 22, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

* properties with object initializers
* constants containing object references
* default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
iluuu1994 pushed a commit to Ocramius/php-src that referenced this issue Apr 22, 2022
This fix corrects a behavior of `var_export()` that was mostly "hidden" until PHP 8.1 introduced:

* properties with object initializers
* constants containing object references
* default values of class properties containing `enum`s

Since `var_export(..., true)` is mostly used in conjunction with code generation,
and we cannot make assumptions about the generated code being placed in the root
namespace, we must always provide the FQCN of a class in exported code.

For example:

```php
<?php

namespace MyNamespace { class Foo {} }

namespace { echo "<?php\n\nnamespace Example;\n\n" . var_export(new \MyNamespace\Foo(), true) . ';'; }
```

produces:

```php
<?php

namespace Example;

MyNamespace\Foo::__set_state(array(
));
```

This code snippet is invalid, because `Example\MyNamespace\Foo::__set_state()` (which
does not exist) is called.

With this patch applied, the code looks like following (valid):

```php
<?php

namespace Example;

\MyNamespace\Foo::__set_state(array(
));
```

Ref: php#8232
Ref: Ocramius/ProxyManager#754
@Ocramius
Copy link
Contributor Author

Thanks @iluuu1994!

@AnrDaemon
Copy link

Do I get it right that this issue was resolved?
Just been bitten by it myself.

@Ocramius
Copy link
Contributor Author

Ocramius commented Dec 7, 2022

@AnrDaemon the merge commit is part of 8.2.0

@AnrDaemon
Copy link

Thanks. For pre-8.2 solution could be

<?=is_object($value) && version_compare(PHP_VERSION, '8.2.0', '<') ? "\\" : ''?><?=var_export($value, true)?>,

@KapitanOczywisty
Copy link

KapitanOczywisty commented Dec 7, 2022

Thanks. For pre-8.2 solution could be

<?=is_object($value) && version_compare(PHP_VERSION, '8.2.0', '<') ? "\\" : ''?><?=var_export($value, true)?>,

This is not valid in some cases, is_object is testing for any object, not just for enums. instanceof \UnitEnum should be used instead. https://3v4l.org/K68DW

Edit: Only stdClass is an exception, so is_object($value) && $value::class !== 'stdClass'

@AnrDaemon
Copy link

What case you are thinking about is not valid?

@KapitanOczywisty
Copy link

KapitanOczywisty commented Dec 8, 2022

@AnrDaemon Sorry, I didn't realize objects were also affected, so the problem is only with stdClass: https://3v4l.org/dJV8T

@AnrDaemon
Copy link

Yup, that seems the only special case. (As is the stdClass itself.) But in controlled environment (i.e. where you are sure you won't encounter stdClass ever), this workaround is applicable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants