A powerful and pretty replacement for PHP's var_export()

Overview

Brick\VarExporter

A powerful and pretty replacement for PHP's var_export().

Build Status Coverage Status Latest Stable Version Total Downloads License

Introduction

PHP's var_export() function is a handy way to export a variable as executable PHP code.

It is particularly useful to store data that can be cached by OPCache, just like your source code, and later retrieved very fast, much faster than unserializing data using unserialize() or json_decode().

But it also suffers from several drawbacks:

  • It outputs invalid PHP code for stdClass objects, using stdClass::__set_state() which doesn't exist (PHP < 7.3)
  • It cannot export custom objects that do not implement __set_state(), and __set_state() does not play well with private properties in parent classes, which makes the implementation tedious
  • It does not support closures

Additionally, the output is not very pretty:

  • It outputs arrays as array() notation, instead of the short [] notation
  • It outputs numeric arrays with explicit and unnecessary 0 => ... key => value syntax

This library aims to provide a prettier, safer, and powerful alternative to var_export().
The output is valid and standalone PHP code, that does not depend on the brick/varexporter library.

Installation

This library is installable via Composer:

composer require brick/varexporter

Requirements

This library requires PHP 7.2 or later.

Project status & release process

While this library is still under development, it is well tested and should be stable enough to use in production environments.

The current releases are numbered 0.x.y. When a non-breaking change is introduced (adding new methods, optimizing existing code, etc.), y is incremented.

When a breaking change is introduced, a new 0.x version cycle is always started.

It is therefore safe to lock your project to a given release cycle, such as 0.3.*.

If you need to upgrade to a newer release cycle, check the release history for a list of changes introduced by each further 0.x.0 version.

Quickstart

This library offers a single method, VarExporter::export() which works pretty much like var_export():

use Brick\VarExporter\VarExporter;

echo VarExporter::export([1, 2, ['foo' => 'bar', 'baz' => []]]);

This code will output:

[
    1,
    2,
    [
        'foo' => 'bar',
        'baz' => []
    ]
]

Compare this to the var_export() output:

array (
  0 => 1,
  1 => 2,
  2 => 
  array (
    'foo' => 'bar',
    'baz' => 
    array (
    ),
  ),
)

Note: unlike var_export(), export() always returns the exported variable, and never outputs it.

Exporting stdClass objects

You come across a stdClass object every time you cast an array to an object, or use json_decode() with the second argument set to false (which is the default).

While the output of var_export() for stdClass is syntactically valid PHP code:

var_export(json_decode('
    {
        "foo": "bar",
        "baz": {
            "hello": "world"
        }
    }
'));
stdClass::__set_state(array(
   'foo' => 'bar',
   'baz' => 
  stdClass::__set_state(array(
     'hello' => 'world',
  )),
))

it is totally useless as it assumes that stdClass has a static __set_state() method, when it doesn't:

Error: Call to undefined method stdClass::__set_state()

What does VarExporter do instead?

It outputs an array to object cast, which is syntactically valid, readable and executable:

echo VarExporter::export(json_decode('
    {
        "foo": "bar",
        "baz": {
            "hello": "world"
        }
    }
'));
(object) [
    'foo' => 'bar',
    'baz' => (object) [
        'hello' => 'world'
    ]
]

Note: since PHP 7.3, var_export() now exports an array to object cast like VarExporter::export() does.

Exporting custom objects

As we've seen above, var_export() assumes that every object has a static __set_state() method that takes an associative array of property names to values, and returns a object.

This means that if you want to export an instance of a class outside of your control, you're screwed up. This also means that you have to write boilerplate code for your classes, that looks like:

class Foo
{
    public $a;
    public $b;
    public $c;

    public static function __set_state(array $array) : self
    {
        $object = new self;

        $object->a = $array['a'];
        $object->b = $array['b'];
        $object->c = $array['c'];

        return $object;
    }
}

Or the more dynamic, reusable, and less IDE-friendly version:

public static function __set_state(array $array) : self
{
    $object = new self;

    foreach ($array as $key => $value) {
        $object->{$key} = $value;
    }

    return $object;
}

If your class has a parent with private properties, you may have to do some gymnastics to write the value, and if your class overrides a private property of one of its parents, you're out of luck as var_export() puts all properties in the same bag, outputting an array with a duplicate key.

What does VarExporter do instead?

It determines the most appropriate method to export your object, in this order:

  • If your custom class has a __set_state() method, VarExporter uses it by default, just like var_export() would do:

    \My\CustomClass::__set_state([
        'foo' => 'Hello',
        'bar' => 'World'
    ])

    The array passed to __set_state() will be built with the same semantics used by var_export(); this library aims to be 100% compatible in this regard. The only difference is when your class has overridden private properties: var_export() will output an array that contains the same key twice (resulting in data loss), while VarExporter will throw an ExportException to keep you on the safe side.

    Unlike var_export(), this method will only be used if actually implemented on the class.

    You can disable exporting objects this way, even if they implement __set_state(), using the NO_SET_STATE option.

  • If your class has __serialize() and __unserialize() methods (introduced in PHP 7.4, but this library accepts them in previous versions of PHP!), VarExporter uses the output of __serialize() to export the object, and gives it as input to __unserialize() to reconstruct the object:

    (static function() {
        $class = new \ReflectionClass(\My\CustomClass::class);
        $object = $class->newInstanceWithoutConstructor();
    
        $object->__unserialize([
            'foo' => 'Test',
            'bar' => 1234
        ]);
    
        return $object;
    })()

    This method is recommended for exporting complex custom objects: it is forward compatible with the new serialization mechanism introduced in PHP 7.4, flexible, safe, and composes very well under inheritance.

    If for any reason you do not want to export objects that implement __serialize() and __unserialize() using this method, you can opt out by using the NO_SERIALIZE option.

  • If the class does not meet any of the conditions above, it is exported through direct property access, which in its simplest form looks like:

    (static function() {
        $object = new \My\CustomClass;
    
        $object->publicProp = 'Foo';
        $object->dynamicProp = 'Bar';
    
        return $object;
    })()

    If the class has a constructor, it will be bypassed using reflection:

    (static function() {
        $class = new \ReflectionClass(\My\CustomClass::class);
        $object = $class->newInstanceWithoutConstructor();
    
        ...
    })()

    If the class has non-public properties, they will be accessed through closures bound to the object:

    (static function() {
        $class = new \ReflectionClass(\My\CustomClass::class);
        $object = $class->newInstanceWithoutConstructor();
    
        $object->publicProp = 'Foo';
        $object->dynamicProp = 'Bar';
    
        (function() {
            $this->protectedProp = 'contents';
            $this->privateProp = 'contents';
        })->bindTo($object, \My\CustomClass::class)();
    
        (function() {
            $this->privatePropInParent = 'contents';
        })->bindTo($object, \My\ParentClass::class)();
    
        return $object;
    })()

    You can disable exporting objects this way, using the NOT_ANY_OBJECT option.

If you attempt to export a custom object and all compatible exporters have been disabled, an ExportException will be thrown.

Exporting closures

Since version 0.2.0, VarExporter has experimental support for closures:

echo VarExporter::export([
    'callback' => function() {
        return 'Hello, world!';
    }
]);
[
    'callback' => function () {
        return 'Hello, world!';
    }
]

To do this magic, VarExporter parses the PHP source file where your closure is defined, using the well-established nikic/php-parser library, inspired by SuperClosure.

To ensure that the closure will work in any context, it rewrites its source code, replacing any namespaced class/function/constant name with its fully qualified counterpart:

namespace My\App;

use My\App\Model\Entity;
use function My\App\Functions\imported_function;
use const My\App\Constants\IMPORTED_CONSTANT;

use Brick\VarExporter\VarExporter;

echo VarExporter::export(function(Service $service) : Entity {
    strlen(NON_NAMESPACED_CONSTANT);
    imported_function(IMPORTED_CONSTANT);
    \My\App\Functions\explicitly_namespaced_function(\My\App\Constants\EXPLICITLY_NAMESPACED_CONSTANT);

    return new Entity();
});
function (\My\App\Service $service) : \My\App\Model\Entity {
    strlen(NON_NAMESPACED_CONSTANT);
    \My\App\Functions\imported_function(\My\App\Constants\IMPORTED_CONSTANT);
    \My\App\Functions\explicitly_namespaced_function(\My\App\Constants\EXPLICITLY_NAMESPACED_CONSTANT);
    return new \My\App\Model\Entity();
}

Note how all namespaced classes, and explicitly namespaced functions and constants, have been rewritten, while the non-namespaced function strlen() and the non-namespaced constant have been left as is. Please see the first caveat.

Use statements

By default, exporting closures that have variables bound through use() will throw an ExportException. This is intentional, because exported closures can be executed in another context, and as such must not rely on the context they've been originally defined in.

When using the CLOSURE_SNAPSHOT_USES option, VarExporter will export the current value of each use() variable instead of throwing an exception. The exported variables are added as expression inside the exported closure.

$planet = 'world';

echo VarExporter::export([
    'callback' => function(string $greeting) use ($planet) {
        return $greeting . ', ' . $planet . '!';
    }
], VarExporter::CLOSURE_SNAPSHOT_USE);
[
    'callback' => function (string $greeting) {
        $planet = 'world';
        return $greeting . ', ' . $planet . '!';
    }
]

Arrow functions

PHP supports shorthand syntax for closures (since PHP 7.4), also known as arrow functions. VarExporter will export these as normal closures.

Arrow functions can implicitly use variables from the context they've been defined in. If any context variable is used in the arrow function, VarExporter will throw an ExportException unless the CLOSURE_SNAPSHOT_USES option is used.

$planet = 'world';

echo VarExporter::export([
    'callback' => fn(string $greeting) => $greeting . ', ' . $planet . '!';
], VarExporter::CLOSURE_SNAPSHOT_USES);
[
    'callback' => function (string $greeting) {
        $planet = 'world';
        return $greeting . ', ' . $planet . '!';
    }
]

Caveats

  • Functions and constants that are not explicitly namespaced, either directly or through a use function or use const statement, are always exported as is. This is because the parser does not have the runtime context to check if a definition for this function or constant exists in the current namespace, and as such cannot reliably predict the behaviour of PHP's fallback to global function/constant. Be really careful here if you're using namespaced functions or constants: always explicitly import your namespaced functions and constants, if any.
  • Closures can use $this, but will not be bound to an object once exported. You must explicitly bind them through bindTo() if required, after running the exported code.
  • You cannot have 2 closures on the same line in your source file, or an ExportException will be thrown. This is because VarExporter cannot know which one holds the definition for the \Closure object it encountered.
  • Closures defined in eval()'d code cannot be exported and throw an ExportException, because there is no source file to parse.

You can disable exporting closures, using the NO_CLOSURES option. When this option is set, an ExportException will be thrown when attempting to export a closure.

Options

VarExporter::export() accepts a bitmask of options as a second parameter:

VarExporter::export($var, VarExporter::ADD_RETURN | VarExporter::ADD_TYPE_HINTS);

Available options:

VarExporter::ADD_RETURN

Wraps the output in a return statement:

return (...);

This makes the code ready to be executed in a PHP file―or eval(), for that matter.

VarExporter::ADD_TYPE_HINTS

Adds type hints to objects created through reflection, and to $this inside closures bound to an object. This allows the resulting code to be statically analyzed by external tools and IDEs:

/** @var \My\CustomClass $object */
$object = $class->newInstanceWithoutConstructor();

(function() {
    /** @var \My\CustomClass $this */
    $this->privateProp = ...;
})->bindTo($object, \My\CustomClass::class)();

VarExporter::SKIP_DYNAMIC_PROPERTIES

Skips dynamic properties on custom classes in the output. Dynamic properties are properties that are not part of the class definition, and added to an object at runtime. By default, any dynamic property set on a custom class is exported; if this option is used, dynamic properties are only allowed on stdClass objects, and ignored on other objects.

VarExporter::NO_SET_STATE

Disallows exporting objects through __set_state().

VarExporter::NO_SERIALIZE

Disallows exporting objects through __serialize() and __unserialize().

VarExporter::NOT_ANY_OBJECT

Disallows exporting any custom object using direct property access and bound closures.

VarExporter::NO_CLOSURES

Disallows exporting closures.

VarExporter::INLINE_NUMERIC_SCALAR_ARRAY

Formats numeric arrays containing only scalar values on a single line:

VarExporter::export([
    'one' => ['hello', 'world', 123, true, false, null, 7.5],
    'two' => ['hello', 'world', ['one', 'two', 'three']]
], VarExporter::INLINE_NUMERIC_SCALAR_ARRAY);
[
    'one' => ['hello', 'world', 123, true, false, null, 7.5],
    'two' => [
        'hello',
        'world',
        ['one', 'two', 'three']
    ]
]

Types considered scalar here are int, bool, float, string and null.

VarExporter::TRAILING_COMMA_IN_ARRAY

Adds a trailing comma after the last item of non-inline arrays:

VarExporter::export(
    ['hello', 'world', ['one', 'two', 'three']],
    VarExporter::TRAILING_COMMA_IN_ARRAY | VarExporter::INLINE_NUMERIC_SCALAR_ARRAY
);
[
    'hello',
    'world',
    ['one', 'two', 'three'],
]

VarExporter::CLOSURE_SNAPSHOT_USES

Export the current value of each use() variable as expression inside the exported closure.

Indentation

You can use the 3rd argument of VarExporter::export() to control the indentation level. This is useful when you want to use the generated code string to replace a placeholder in a template used to generate code files.

So using output of VarExporter::export(['foo' => 'bar'], indentLevel: 1) in the template below to replace {{exported}}:

public foo() 
{
    $data = {{exported}};
}

would result in:

public foo() 
{
    $data = [
        'foo' => 'bar'
    ];
}

Note that the first line will never be indented, as we can see in the example above.

Error handling

Any error occurring on export() will throw an ExportException:

use Brick\VarExporter\VarExporter;
use Brick\VarExporter\ExportException;

try {
    VarExporter::export(fopen('php://memory', 'r'));
} catch (ExportException $e) {
    // Type "resource" is not supported.
}

Limitations

  • Exporting internal classes other than stdClass and Closure, and classes implementing __set_state() (most notably DateTime classes) is currently not supported. VarExporter will throw an ExportException if it finds one.

    To avoid hitting this brick wall, you can implement __serialize() and __unserialize() in classes that contain references to internal objects.

    Feel free to open an issue or a pull request if you think that an internal class could/should be exportable.

  • Exporting anonymous classes is not supported yet. Ideas or pull requests welcome.

  • Just like var_export(), VarExporter cannot currently maintain object identity (two instances of the same object, once exported, will create two equal (==) yet distinct (!==) objects).

  • And just like var_export(), it cannot currently handle circular references, such as object A pointing to B, and B pointing back to A.

In pretty much every other case, it offers an elegant and very efficient way to cache data to PHP files, and a solid alternative to serialization.

Comments
  • Add argument to control the indentation level.

    Add argument to control the indentation level.

    POC for generating exported code with additional indentation.

    The idea is to be able to replace placeholder in templates with the exported code string and end up with properly indented PHP code in the generated file.

    So using output of VarExporter::exportIndented(['foo' => 'bar'], 0, 1) in template below

    public foo () 
    {
        $aVar = {{exported}};
    }
    

    would result in

    public foo () 
    {
        $aVar = [
            'foo' => 'bar'
        ];
    }
    

    I can finish up the PR if you like the idea in general.

    opened by ADmad 23
  • Option to export numeric arrays all-on-one-line

    Option to export numeric arrays all-on-one-line

    I'm really liking this lib!

    For my use-case, I have large numeric (non-assoc) arrays of numbers that I'm exporting. I'd like an option that would avoid new-lines + indentation for non-numeric arrays to save a ton of lines in the output.

    An option to make numeric arrays stay on one line and keep associative arrays multi-line would be the ideal output for me.

    Thanks for your work on this!

    opened by francislavoie 6
  • Add support for exporting Enums when using PHP8.1+

    Add support for exporting Enums when using PHP8.1+

    This enables support for exporting Enums when using PHP8.1+

    Enums are considered objects which cannot be instantiated, this caused a fatal error when using the var exporter:

          Error::("Cannot instantiate enum XY")
    

    This pull requests enables support for exporting Enums, disabling exporting of Enums & keeps support for previous PHP versions without breaking changes.

    opened by Jacobs63 5
  • Supported exporting closures with use() variables

    Supported exporting closures with use() variables

    Added the ~~CLOSURE_USE_AS_VAR~~ CLOSURE_SNAPSHOT_USE option to allow exporting closures with use() variables. Reflection is used to get the (current) value of these variables. These are exported by the exporter, parsed and inserted as statements.

    Example

    $planet = 'world';
    echo VarExporter::export([
        'callback' => function() use ($planet) {
            return 'Hello, ' . $planet . '!';
        }
    ], VarExported::CLOSURE_SNAPSHOT_USE);
    
    [
        'callback' => function () {
            $planet = 'world';
            return 'Hello, ' . $planet . '!';
        }
    ]
    

    Added ClosureExporter::getParser() method, not to create multiple parsers for exporting a single closure.

    opened by jasny 5
  • Internal Class / DateTime object support

    Internal Class / DateTime object support

    Hello there,

    I'm currently using VarExporter for a caching library.

    I'm caching an PHP object graph and just noticed that a DateTime object at any point of the object structure causes an exception to be thrown. Just then I read about the limitation of "internal" classes, which are described to cause that kind of behavior in this library.

    So my question is, are there plans to support the DateTime object itself and the "internal" objects related to it (DateInterval and so on)? These classes could be supported by the SetStateExporter, as they do implement the __set_state method (php.net).

    Currently I'm using a rather ugly approach to avoid this issue: I'm extending the DateTime class with an own non-"internal" class, which for now does the job. But as I said, it's not very pretty to do such stuff. Especially when using the static createFromFormat() function, which resulted in following code:

    <?php
    
    namespace App;
    
    use App\Workaround\DateTime;
    use DateTime as DateTimeInternal;
    
    // ...
    
    /** @var DateTimeInternal $dateTime */
    $dateTime = DateTime::createFromFormat(static::DTS_FORMAT, $string) ?: null;
    
    return (new DateTime())
        ->setTimestamp($dateTime->getTimestamp());
    
    // ...
    
    <?php
    
    namespace App\Workaround;
    
    use DateTime as DateTimeInternal;
    
    class DateTime extends DateTimeInternal
    {
    }
    

    EDIT: Additionally, this workaround does not work, when trying to re-cache an object, as the code produced by VarExporter looks as follows:

    <?php
    
    // ...
    
    $this->date = \App\Workaround\DateTime::__set_state([
        'date' => '2020-03-03 21:49:31.000000',
        'timezone_type' => 3,
        'timezone' => 'UTC'
    ]);
    
    // ...
    

    This results in yet another DateTime object (the internal one), which cannot be exported afterwards. That forced me to extend all static functions of the internal DateTime object to counter this issue.

    opened by GameplayJDK 3
  • Detect circular references and throw an exception

    Detect circular references and throw an exception

    Currently, circular references create an endless loop and error:

    PHP Fatal error: Allowed memory size of (...) bytes exhausted (...)

    We need to detect these and throw an ExportException in this case.

    opened by BenMorel 3
  • Support for arrow functions

    Support for arrow functions

    This PR includes the commit of #7

    Support shorthand syntax for closures aka arrow functions by converting them to regular closures.

    Reflection is used to check if the arrow function uses any context variable. These are converted to use() variables for the closure.

    Example;

    $planet = 'world';
    
    echo VarExporter::export([
        'callback' => fn(string $greeting) => $greeting . ', ' . $planet . '!';
    ], VarExported::CLOSURE_SNAPSHOT_USES);
    
    [
        'callback' => function (string $greeting) {
            $planet = 'world';
            return $greeting . ', ' . $planet . '!';
        }
    ]
    
    opened by jasny 2
  • var_export vs brick/varexporter

    var_export vs brick/varexporter

    I was looking for a fast methode to store data as php class (OPcache usage). So i tested this class and compared to var_export($data,true)

    i used a array with 5.000 nested arrays:

    {"60001": {
      "id": 958,
      "first_name": "Brynne",
      "last_name": "Whitmore",
      "email": "[email protected]",
      "gender": "Female",
      "tags": [
        "Lumatil",
        "Daping"
      ],
      "log": [
        {
          "ip": "201.167.237.17",
          "user_agent": "Mozilla\/5.0 (X11; CrOS i686 12.433.109) AppleWebKit\/534.30 (KHTML, like Gecko) Chrome\/12.0.742.93 Safari\/534.30"
        },
        {
          "ip": "102.36.242.165",
          "user_agent": "Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit\/535.1 (KHTML, like Gecko) Chrome\/14.0.803.0 Safari\/535.1"
        },
        {
          "ip": "237.146.197.92",
          "user_agent": "Mozilla\/5.0 (Windows; U; Windows NT 5.1; ru-RU) AppleWebKit\/533.19.4 (KHTML, like Gecko) Version\/5.0.3 Safari\/533.19.4"
        },
        {
          "ip": "238.3.93.48",
          "user_agent": "Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit\/535.1 (KHTML, like Gecko) Chrome\/13.0.782.24 Safari\/535.1"
        }
      ],
      "__id": "60001"
    }}
    

    PHP 7.4.6

    VarExporter::export($data) Runtime float(11154.134988785) ms memory_get_usage float(19.297637939453) mb memory_get_peak_usage float(86.893058776855) mb

    var_export($data,true) Runtime float(58.534860610962) ms memory_get_usage float(19.287109375) mb memory_get_peak_usage float(24.960289001465) mb

    PHP 7.3.18

    VarExporter::export($data) Runtime float(11117.231845856) ms memory_get_usage float(18.097465515137) mb memory_get_peak_usage float(86.556076049805) mb

    var_export($data,true) Runtime float(57.005882263184) ms memory_get_usage float(19.997711181641) mb memory_get_peak_usage float(25.753318786621) mb

    PHP 7.2.31

    VarExporter::export($data) Runtime float(11246.009111404) ms memory_get_usage float(20.75756072998) mb memory_get_peak_usage float(87.220108032227) mb

    var_export($data,true) Runtime float(63.722848892212) ms memory_get_usage float(20.657043457031) mb memory_get_peak_usage float(28.439994812012) mb

    All VarExporter::export have a high variation in the runtime (+/-3sec) The var_export have +/-1ms variation.

    I think this could be a good solution to create a opcache class. But if anyone wana store data as PHP, thats not a good solution. The runtime over 10 seconds is realy heavy.

    opened by Steinweber 1
  • Resolves issue #2

    Resolves issue #2

    Changed log

    • It's related to issue #2.
    • Bump to php-7.2 and use the new feature about spl_object_id.
    • Upgrade PHPUnit 8.0 version to support future stable version.
    opened by peter279k 1
  • [Withdrawn] Downgrade PHP requirement from 7.1 to 7.0

    [Withdrawn] Downgrade PHP requirement from 7.1 to 7.0

    (This PR is bad, and will be closed. I did something incorrectly in my env setup - the tests don't run correctly under 7.0.)

    The code appears to be compatible with PHP 7.0, so this PR only includes changes to a few support files to allow installation and CI under PHP 7.0. Code coverage remains 100% under the older version of PHPUnit.

    This is for people and organisations such as myself that run Debian stable in production environments, whose released PHP version is 7.0.

    opened by jdpanderson 1
  • PHP 8.1: Unable to load classes with public readonly properties

    PHP 8.1: Unable to load classes with public readonly properties

    When loading a class with readonly properties, an error is thrown: PHP Fatal error: Uncaught Error: Cannot initialize readonly property ...

    This happens, because readonly properties

    TestCase:

    <?php
    
    require 'vendor/autoload.php';
    
    use Brick\VarExporter\VarExporter;
    
    class Test
    {
        public function __construct(
            public readonly string $foo
        ) {
        }
    }
    
    $object = new Test('bar');
    $string = VarExporter::export($object, VarExporter::ADD_RETURN);
    echo $string;
    
    $newObject = eval($string);
    echo "yay!";
    

    Output

    return (static function() {
        $class = new \ReflectionClass(\Test::class);
        $object = $class->newInstanceWithoutConstructor();
    
        $object->foo = 'bar';
    
        return $object;
    })();
    
    Fatal error: Uncaught Error: Cannot initialize readonly property Test::$foo from global scope in  test.php(19) : eval()'d code on line 5
    
    Error: Cannot initialize readonly property Test::$foo from global scope in test.php(19) : eval()'d code on line 5
    

    Expected Output

    return (static function() {
        $class = new \ReflectionClass(\Test::class);
        $object = $class->newInstanceWithoutConstructor();
        (function() {
            $this->foo = 'bar';
        })->bindTo($object, \Test::class)();
    
        return $object;
    })();yay!
    

    Possible solution

    instead of just setting the property on the object via $object->foo = 'bar' it should be set via bindTo:

    (function() {
            $this->foo = 'bar';
        })->bindTo($object, \Test::class)();
    

    one could use $property-> isReadOnly to determine if this is necessary.

    opened by AnnaDamm 0
  • Exporting closure containing `self` leads to fatal error

    Exporting closure containing `self` leads to fatal error

    Hey there,

    I have the following (reduced) example of our application which uses this component to dump the application config to a filesystem cache:

    <?php
    
    use Brick\VarExporter\VarExporter;
    
    require __DIR__ . '/vendor/autoload.php';
    
    $dataProvider = new class {
        public const CONFIGURATION_ENTRY = 'data-provider-specific-config';
    
        public function getConfig(): array
        {
            return [
                self::CONFIGURATION_ENTRY => [
                    'foo' => 'bar',
                ],
                'factories' => [
                    'MyService' => static function (array $config): object {
                        $configForMyDataProvider = $config[self::CONFIGURATION_ENTRY];
    
                        return new class ($configForMyDataProvider['foo']) {
    
                            public string $foo;
    
                            public function __construct(string $foo)
                            {
                                $this->foo = $foo;
                            }
                        };
                    },
                ],
            ];
        }
    };
    
    $config = array_merge(
        [],
        $dataProvider->getConfig(),
        // Other data providers providing stuff
    );
    
    $content = "<?php\n" . VarExporter::export(
        $config,
        VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES
    );
    
    file_put_contents('cached-config.php', $content);
    
    $configFromFilesystem = require 'cached-config.php';
    
    ($configFromFilesystem['factories']['MyService'])($configFromFilesystem);
    

    So the exporter generates this code:

    <?php
    return [
        'data-provider-specific-config' => [
            'foo' => 'bar'
        ],
        'factories' => [
            'MyService' => static function (array $config) : object {
                $configForMyDataProvider = $config[self::CONFIGURATION_ENTRY];
                return new class($configForMyDataProvider['foo'])
                {
                    public string $foo;
                    public function __construct(string $foo)
                    {
                        $this->foo = $foo;
                    }
                };
            }
        ]
    ];
    

    Overall, no real problem and everything works until the closure is being used to instantiate the service. PHP gives us the follow fatal error:

    Fatal error: Uncaught Error: Cannot access self:: when no class scope is active in /project/cached-config.php:8
    

    I wonder if it is possible to convert the self reference to some kind of FQCN so that we do not have to care about using self or not.

    I am willing to contribute a fix. Just wanted to raise the issue first.

    Feel free to give me some feedback whenever you find some time. Thanks!

    opened by boesing 2
  • Throw exception on unsafe closure export by default

    Throw exception on unsafe closure export by default

    Currently we export non-namespaced functions/constants as is. The exporter closure is not guaranteed to work everywhere. We should instead throw an exception in this case, and make the current behaviour optional by providing a flag, such as ALLOW_UNSAFE_CLOSURE_EXPORT.

    opened by BenMorel 0
Releases(0.3.7)
  • 0.3.7(Jun 29, 2022)

    New feature

    • New option: VarExporter::INLINE_ARRAY

    🗑️ Deprecated

    • The VarExporter::INLINE_NUMERIC_SCALAR_ARRAY is deprecated, please use INLINE_SCALAR_LIST instead
    Source code(tar.gz)
    Source code(zip)
  • 0.3.6(Jun 15, 2022)

  • 0.3.5(Feb 10, 2021)

  • 0.3.4(Feb 7, 2021)

  • 0.3.3(Dec 24, 2020)

  • 0.3.2(Mar 13, 2020)

    New feature

    Support for exporting internal classes implementing __set_state():

    • DateTime
    • DateTimeImmutable
    • DateTimeZone
    • DateInterval
    • DatePeriod

    Thanks @GameplayJDK!

    Source code(tar.gz)
    Source code(zip)
  • 0.3.1(Jan 23, 2020)

    New features

    • Support for closures with use() using the CLOSURE_SNAPSHOT_USE option (#7)
    • Support for arrow functions in PHP 7.4 (#8)

    Thanks to @jasny for his awesome work!

    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Dec 24, 2019)

  • 0.2.1(Apr 16, 2019)

  • 0.2.0(Apr 9, 2019)

    New feature

    • Experimental support for closures 🎉

    💥 Minor BC break

    • export() does not throw an exception anymore when encountering a Closure.
      To get the old behaviour back, use the NO_CLOSURES option.
    Source code(tar.gz)
    Source code(zip)
  • 0.1.2(Apr 8, 2019)

    🐛 Bug fixes

    • Static properties in custom classes were wrongly included—unset()—in the output

    Improvements

    • Circular references are now detected, and throw an ExportException instead of erroring.
    Source code(tar.gz)
    Source code(zip)
  • 0.1.1(Apr 8, 2019)

    🐛 Bug fixes

    • Single-letter properties were wrongly exported using ->{'x'} notation.

    Improvements

    • Exception messages now contain the path (array keys / object properties) to the failure:

      [foo][bar][0] Type "resource" is not supported.

    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Apr 7, 2019)

Owner
Brick
Building blocks for professional PHP applications
Brick
Highly customizable alternative to var_export for PHP code generation

PHP Variable Exporter PHPEncoder is a PHP library for exporting variables and generating PHP code representations for said variables similar to the bu

Riikka Kalliomäki 71 Dec 30, 2022
A super simple, clean and pretty error handler that replace the default error handler of PHP. You need only include this file!

php-custom-error-handler A super simple, clean and pretty error handler that replace the default error handler of PHP. You need just include only this

null 6 Nov 7, 2022
A replacement for Octoprint's UI and for multiple printers.

About Laravel Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experie

Andy Newhouse 1 Jan 19, 2022
This is a replacement dashboard for the Pisces Helium Miner

Pisces QoL Dashboard This is a replacement dashboard for the Pisces Helium Miner. The dashboard that ships with the Pisces P100 has a number of securi

null 42 Jun 16, 2022
Much improved replacement for Zend_Cache_Backend_File - works great with Magento!

Cm_Cache_Backend_File The stock Zend_Cache_Backend_File backend has extremely poor performance for cleaning by tags making it become unusable as the n

Colin Mollenhour 146 Aug 22, 2022
Easily create WooCommerce replacement orders for your customers.

Support Orders for WooCommerce Requires PHP: 7.0 WP requires at least: 5.7 WP tested up to: 5.7 WC requires at least: 5.6.0 WC tested up to: 5.8.0 Sta

DevPress 5 Feb 24, 2022
A drop-in replacement for the Magento 2 checkout.

Clean Checkout for Magento 2 A drop-in replacement for the Magento 2 checkout. Features Modules The project is divided into several modules: Overall c

Daniel Sloof 275 Sep 14, 2022
A faster drop in replacement for bin/magento cache:clean with file watcher

"You know, hope is a mistake. If you can't fix what's broken, you'll, uh... you'll go insane." - Max Rockatansky Magento 2 Cache Clean A faster drop i

mage2tv 460 Dec 26, 2022
Igbinary is a drop in replacement for the standard php serializer.

igbinary Igbinary is a drop in replacement for the standard php serializer. Instead of the time and space consuming textual representation used by PHP

Igbinary development 727 Dec 21, 2022
A Symfony2 bundle that integrates Select2 as a drop-in replacement for a standard entity field on a Symfony form.

select2entity-bundle Introduction This is a Symfony bundle which enables the popular Select2 component to be used as a drop-in replacement for a stand

Ross Keatinge 214 Nov 21, 2022
Orangescrum is a simple yet powerful free and open source project management software that helps team to organize their tasks, projects and deliver more.

Free, open source Project Management software Introduction Orangescrum is the simple yet powerful free and open source project management software tha

Orangescrum 110 Dec 30, 2022
Powerful and flexible component for building site breadcrumbs.

Phalcon Breadcrumbs Phalcon Breadcrumbs is a powerful and flexible component for building site breadcrumbs. You can adapt it to your own needs or impr

Serghei Iakovlev 40 Nov 5, 2022
A powerful debug and profilers tool for the Phalcon Framework

Phalcon Debugbar Integrates PHP Debug Bar with Phalcon Framework. 中文说明 Features Normal request capturing Ajax request capturing Redirect request chain

Yajie Zhu 162 Oct 7, 2022
A library of powerful code snippets to help you get the job done with Gravity Forms and Gravity Perks.

Gravity Wiz Snippet Library Gravity Wiz is creating the most comprehensive library of snippets for Gravity Forms ever. We'll be consistently moving ou

Gravity Wiz 151 Dec 27, 2022
Sentrifugo is a FREE and powerful Human Resource Management System (HRMS) that can be easily configured to meet your organizational needs.

Sentrifugo Sentrifugo is a free and powerful new-age Human Resource Management System that can be easily configured to adapt to your organizational pr

Sentrifugo 447 Dec 27, 2022
The new, most powerful Comic Reader ever created by the human race. Reworked by an Otaku.

FoOlSlideX The new, most powerful Comic Reader ever created by the human race. Reworked by an Otaku. Requirements PHP greater than 7.0 and everything

saintly2k 21 Dec 21, 2022
Sey is a powerful math interpreter with infinite-precision.

Sey, pronounce say, is a powerful math interpreter with infinite-precision.

Félix Dorn 13 Oct 1, 2022
The fastest way to make a powerful JSON:API compatible Rest API with Laravel.

The first fully customizable Laravel JSON:API builder. "CRUD" and protect your resources with 0 (zero) extra line of code. Installation You can instal

BinarCode 394 Dec 23, 2022