ProxyManager v2.0.0 Release Notes

  • BC Breaks

    ๐Ÿ“š Please refer to [the upgrade documentation](UPGRADE.md) to see which backwards-incompatible ๐Ÿš€ changes were applied to this release.

    ๐Ÿ†• New features

    ๐Ÿ‘ PHP 7 support

    ProxyManager will now correctly operate in PHP 7 environments.

    PHP 7 Return type hints

    ProxyManager will now correctly mimic signatures of methods with return type hints:

    class SayHello
    {
        public function hello() : string
        {
            return 'hello!';
        }
    }
    

    PHP 7 Scalar type hints

    ProxyManager will now correctly mimic signatures of methods with scalar type hints

    class SayHello
    {
        public function hello(string $name) : string
        {
            return 'hello, ' . $name;
        }
    }
    

    ๐Ÿ‘ PHP 5.6 Variadics support

    ProxyManager will now correctly mimic behavior of methods with variadic parameters:

    class SayHello
    {
        public function hello(string ...$names) : string
        {
            return 'hello, ' . implode(', ', $names);
        }
    }
    

    ๐Ÿ‘ By-ref variadic arguments are also supported:

    class SayHello
    {
        public function hello(string &...$names)
        {
            foreach ($names as & $name) {
                $name = 'hello, ' . $name;
            }
        }
    }
    

    Constructors in proxies are not replaced anymore

    In ProxyManager v1.x, the constructor of a proxy was completely replaced with a method accepting proxy-specific parameters.

    This is no longer true, and you will be able to use the constructor of your objects as if the class wasn't proxied at all:

    <?php
    
    class SayHello
    {
        public function __construct()
        {
            echo 'Hello!';
        }
    }
    
    /* @var $proxyGenerator \ProxyManager\ProxyGenerator\ProxyGeneratorInterface */
    $proxyClass = $proxyGenerator->generateProxy(
        new ReflectionClass(SayHello::class),
        new ClassGenerator('ProxyClassName')
    );
    
    eval('<?php ' . $proxyClass->generate());
    
    $proxyName = $proxyClass->getName();
    $object = new ProxyClassName(); // echoes "Hello!"
    
    var_dump($object); // a proxy object
    

    ๐Ÿ— If you still want to manually build a proxy (without factories), a public static staticProxyConstructor method is added to the generated proxy classes.

    ๐Ÿ‘ Friend classes support

    You can now access state of "friend objects" at any time.

    class EmailAddress
    {
        private $address;
    
        public function __construct(string $address)
        {
            assertEmail($address);
    
            $this->address = $address;
        }
    
        public function equalsTo(EmailAddress $other)
        {
            return $this->address === $other->address;
        }
    }
    

    When using lazy-loading or access-interceptors, the equalsTo method will properly work, as even protected and private access are now correctly proxied.

    Ghost objects now only lazy-load on state-access

    Lazy loading ghost objects now trigger lazy-loading only when their state is accessed. This also implies that lazy loading ghost objects cannot be used with interfaces anymore.

    class AccessPolicy
    {
        private $policyName;
    
        /**
         * Calling this method WILL cause lazy-loading, when using a ghost object,
         * as the method is accessing the object's state
         */
        public function getPolicyName() : string
        {
            return $this->policyName;        
        }
    
        /**
         * Calling this method WILL NOT cause lazy-loading, when using a ghost object,
         * as the method is not reading any from the object.
         */
        public function allowAccess() : bool
        {
            return false;
        }
    }
    

    Faster ghost object state initialization

    Lazy loading ghost objects can now be initialized in a more efficient way, by avoiding reflection or setters:

    class Foo
    {
        private $a;
        protected $b;
        public $c;
    }
    
    $factory = new \ProxyManager\Factory\LazyLoadingGhostFactory();
    
    $proxy = $factory-createProxy(
        Foo::class,
        function (
            GhostObjectInterface $proxy, 
            string $method, 
            array $parameters, 
            & $initializer,
            array $properties
        ) {
            $initializer   = null;
    
            $properties["\0Foo\0a"] = 'abc';
            $properties["\0*\0b"]   = 'def';
            $properties['c']        = 'ghi';
    
            return true;
        }
    );
    
    
    $reflectionA = new ReflectionProperty(Foo::class, 'a');
    $reflectionA->setAccessible(true);
    
    var_dump($reflectionA->getValue($proxy)); // dumps "abc"
    
    $reflectionB = new ReflectionProperty(Foo::class, 'b');
    $reflectionB->setAccessible(true);
    
    var_dump($reflectionB->getValue($proxy)); // dumps "def"
    
    var_dump($proxy->c); // dumps "ghi"
    

    Skipping lazy-loaded properties in generated proxies

    Lazy loading ghost objects can now skip lazy-loading for certain properties. This is especially useful when you have properties that are always available, such as identifiers of entities:

    class User
    {
        private $id;
        private $username;
    
        public function getId() : int
        {
            return $this->id;
        }
    
        public function getUsername() : string
        {
            return $this->username;
        }
    }
    
    /* @var $proxy User */
    $proxy = (new \ProxyManager\Factory\LazyLoadingGhostFactory())->createProxy(
        User::class,
        function (
            GhostObjectInterface $proxy,
            string $method,
            array $parameters,
            & $initializer,
            array $properties
        ) {
            $initializer   = null;
    
            var_dump('Triggered lazy-loading!');
    
            $properties["\0User\0username"] = 'Ocramius';
    
            return true;
        },
        [
            'skippedProperties' => [
                "\0User\0id",
            ],
        ]
    );
    
    $idReflection = new \ReflectionProperty(User::class, 'id');
    
    $idReflection->setAccessible(true);
    $idReflection->setValue($proxy, 123);
    
    var_dump($proxy->getId());       // 123
    var_dump($proxy->getUsername()); // "Triggered lazy-loading!", then "Ocramius"
    

    0๏ธโƒฃ Proxies are now always generated on-the-fly by default

    ๐Ÿ”ง Proxies are now automatically generated any time you require them: no configuration ๐ŸŽ needed. If you want to gain better performance, you may still want to read ๐Ÿ“„ the [tuning for production docs](docs/tuning-for-production.md).

    Proxy names are now hashed, simplified signature is attached to them

    Proxy classes now have shorter names, as the parameters used to generate them are hashed into their name. A signature is attached to proxy classes (as a private static โšก๏ธ property) so that proxy classes aren't re-used across library updates. โฌ†๏ธ Upgrading ProxyManager will now cause all proxies to be re-generated automatically, while the old proxy files are going to be ignored.