Git Product home page Git Product logo

proxymanager's Introduction

Proxy Manager

A message to Russian ๐Ÿ‡ท๐Ÿ‡บ people

If you currently live in Russia, please read this message.

Purpose

This library aims to provide abstraction for generating various kinds of proxy classes.

ProxyManager

Mutation testing badge Type Coverage

Total Downloads Latest Stable Version Latest Unstable Version

Documentation

You can learn about the proxy pattern and how to use the ProxyManager in the docs.

ocramius/proxy-manager for enterprise

Available as part of the Tidelift Subscription.

The maintainer of ocramius/proxy-manager and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more..

You can also contact the maintainer at [email protected] for looking into issues related to this package in your private projects.

Installation

The suggested installation method is via composer:

php composer.phar require ocramius/proxy-manager

Proxy example

Here's how you build a lazy loadable object with ProxyManager using a Virtual Proxy

$factory = new \ProxyManager\Factory\LazyLoadingValueHolderFactory();

$proxy = $factory->createProxy(
    \MyApp\HeavyComplexObject::class,
    function (& $wrappedObject, $proxy, $method, $parameters, & $initializer) {
        $wrappedObject = new \MyApp\HeavyComplexObject(); // instantiation logic here
        $initializer   = null; // turning off further lazy initialization

        return true; // report success
    }
);

$proxy->doFoo();

See the documentation for more supported proxy types and examples.

proxymanager's People

Contributors

2dsharp avatar blanchonvincent avatar dependabot-preview[bot] avatar fmasa avatar geeh avatar greg0ire avatar gws avatar hultberg avatar localheinz avatar ludo444 avatar malukenho avatar martinssipenko avatar michaelmoussa avatar michaelpetri avatar michalbundyra avatar muglug avatar nicolas-grekas avatar ocramius avatar ondrejmirtes avatar orklah avatar ostrolucky avatar pedro-stanaka avatar raulfraile avatar reiz avatar renovate[bot] avatar samsonasik avatar snapshotpl avatar staabm avatar tobion avatar wouterj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proxymanager's Issues

Handle Initialization Failures

The current lazy loading code sets the initializer to null before the instance is initialized:

public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator)
{
    return $this->factory->createProxy(
        $definition->getClass(),
        function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
            $proxy->setProxyInitializer(null);

            $wrappedInstance = call_user_func($realInstantiator);

            return true;
        }
    );
}

In cases where the constructor for this object could throw an exception, subsequent method calls on this object will attempt to run on a null object.

I feel that the ProxyInitializer should be set to null after the object is initialized. This way other method calls will attempt to initialize the object again if the exception was caught elsewhere.

ValueHolder reflection properties

Currently, there is no real way of providing access to the wrapped object's properties without triggering lazy initialization. A custom ReflectionProperty implementation could do the trick.

Proxy instantiation triggers

So, you like wild ideas - here you go :)
Coming from #32

Idea:

  1. $proxy->proxyObserveInstantiation($callback) to register a callback that will trigger when the proxied object instantiates.
  2. $proxy->proxyScheduleOperation($method, $args) to register a method that to run on the proxied object directly after instantiation.

The typical use cases:

  1. The consumer object starts with a proxy, but on instantiation the proxy triggers a setter method on the consumer, which replaces the proxy with the "real" thing, saving you the indirection on further calls. The is the same idea as in the issue linked above, but done in a cleaner way.
    Usually the indirection would not make such a big difference, but I am working with an example where it is worth it.
  2. This is for methods that only need to run after instantiation of the proxied object. A typical example would be expensive bootstrap operations, which can be postponed, or which can be totally skipped if the proxied object never instantiates.

Here is some code:
http://drupalcode.org/project/xautoload.git/blob/e4cfaf47e16260565815b7ac35c976cd8f87c9e5:/lib/Container/ProxyObject.php
(yes, this needs more doc comments)

Private variable access on a AccessInterceptorValueHolderFactory proxy class throws an error

Whenever attempting to read / write to a private variable execution stops and an error is thrown.

Not entirely sure if you'd want to keep this behaviour, or possibly throw an exception?

Fatal error: Cannot access private property Foo::$privateProperty in {proxyclass} on line 107

Execution stops when attempting to set $foo->privateProperty = 'Baz';

<?php

require "vendor/autoload.php";


class Foo
{
    public $publicArray = array();
    public $publicProperty;

    protected $protectedArray = array();
    protected $protectedProperty;

    private $privateArray = array();
    private $privateProperty;

    public $referencedProperty;

    public function publicMethod($arg)
    {
        return $arg;
    }

    protected function protectedMethod($arg)
    {
        return $arg;
    }

    private function privateMethod($arg)
    {
        return $arg;
    }
}

$config = new \ProxyManager\Configuration();
$factory = new \ProxyManager\Factory\AccessInterceptorValueHolderFactory($config);

$foo = $factory->createProxy(
    new Foo()
);


echo '------------------------------' . PHP_EOL;
$foo->privateProperty = 'Baz';
var_dump($foo->privateProperty);

echo '------------------------------' . PHP_EOL;
$foo->publicProperty = 'Foo';
var_dump($foo->publicProperty);

echo '------------------------------' . PHP_EOL;
$foo->protectedProperty = 'Bar';
var_dump($foo->protectedProperty);

Self-Hydrating object

Problem

Typical data-mappers require us to save and load objects at a very high speed. A typical approach to handle POPO in data mappers is to use reflection:

class A
{
    protected $foo;
}

$field = new ReflectionProperty('A', 'foo');

$field->setAccessible(true);

$a = new A();

$field->setValue($a, 'bar');

Solution

Therefore, a hydrating object may be ideal to handle such cases. It may ideally override the constructor and thus handle data like following:

class AHydratingProxy extends A
{
    public function __construct(array $data)
    {
        $this->foo = $data['foo'];
    }

    public function getHydratingProxyValues()
    {
        return array('foo' => $this->foo);
    }
}

That would speed up instantiation by quite a bit.

Limitations

Does not work on private properties, which have still to be set via reflection

Hydrator property mapping.

Hello there!

I have a few features I want to see if you're interested in implementing before I create my own generators.

Basically, I have data I want to hydrate into an object but with different field names.

i.e.)

$user = $hydrator->hydrate(array('fn' => 'John', 'ln' => 'Doe'), new User());

echo $user->firstName; // "John"
echo $user->lastName; // "Doe"

This shouldn't be too hard to implement i don't think. It'd also be nice to extract it back into the shortened fields. It's just a matter of where to put this configuration or at least make it possible.

I'd like to perhaps add annotations to do the mapping (but I can do that within my application). It'd just be great if it were possible right now. It'd be a simple way to do DTO to domain object mapping and back.

Let me know if you feel this can belong here and I where you feel the configuration should be and I can give you a PR.

Generated Hydrators can avoid reflection completely

It is possible to avoid reflection completely in PHP 5.4 - credits to @asm89 for the initial idea:

class Bar
{
    private $baz = 'tab';
}

class Foo extends Bar
{
    private $baz = 'baz';

    public function __construct()
    {
        $this->accessor = function () { return $this->baz; };
        $this->writer = function ($bar) { $this->baz = $bar; };
    }
}

$foo = new Foo();
$bar = new Bar();

$accessor = $foo->accessor;

var_dump($accessor());

$accessor = Closure::bind($foo->accessor, $bar, 'Bar');

var_dump($accessor());

$writer = Closure::bind($foo->writer, $bar, 'Bar');

$writer('taz');

var_dump($bar);

This trick can also be used to support hydration of objects that are instances of a final class

A working example can be found at http://3v4l.org/L6PhM

Relevant problems in this implementation are:

  • Keep current implementation working with PHP 5.3 (reflection)
  • Skip this entire logic when no private properties exist
  • Create a blueprint instance of the object to be hydrated in order to correcly bind the closure
  • Make sure the blueprint is instantiated in a safe way - should use unserialize and do it safely, should fallback to reflection if __wakeup or Serializable are implemented
  • Handle multiple levels of inheritance - each accessor is should work for one class' private methods.
  • There should only be one accessor per level of inheritance and array_merge could then be used to aggregate results. Eventually consider if multiple accessors is better than array_merge with a benchmark. The accessor may also return a list to avoid the call to array_merge

Consider using PHP-Parser instead of Zend\Code

PHP-Parser looks quite good and could simplify code generation by a lot. It is particularly interesting since it allows working with an AST, which allows further optimizations on generated code (when possible).

Right now, PHP-Parser is not yet stable, so it's still early to decide what to do.

Can't create hydrator and virtual proxy for the same class

At the moment there is no possibility to create hydrator proxy (via HydratorFactory) and virtual proxy (via LazyLoadingValueHolderFactory) for the same class because of one class name inflector instance.

I suggest introducing separate instances of class name inflector for each factory individually ex. with different prefixes.

Evaluating generator strategy should take into account disabled `eval`

The ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy should take into account if eval() was disabled because of disable_functions, and should workaround it.

A possible solution is to write a temporary file and then remove it:

foreach (explode(',', ini_get('disable_functions')) as $removed) {
    if ('eval' === strtolower(trim($removed)) {
       $fileName = system_get_tmp_dir() 
           . '/EvaluatingGeneratorStrategy.php.tmp.' . uniqid();

       file_put_contents($tmpFileName, "<?php\n\n" . $generatedCode);
       require $fileName;
       unlink($fileName);

       return $generatedCode;
    }
}

eval($generatedCode);

return $generatedCode;

Make the evaluating generator strategy a default

The current generator strategy dumps files into the /tmp directory, and is unsafe if the users of ProxyManager don't carefully set a different path per application.

I suggest making the EvaluatingGeneratorStrategy a default.

Add ability to proxy interfaces

The current generators are unable to proxy interfaces - proxying interfaces would be a big plus in general, since that means no proxying of properties has to necessarily happen.

ProxyManager configuration: auto generation and autoloader

I read the Tuning for production doc, but the problem I face is that I can't generate all the proxies beforehand for production.

So I'm wondering what's the best production configuration in this case.

I guess I have to enable autogenerate proxy, but are they always generated (i.e. each time)? A quick look at the code tells me that maybe registering the proxy autoloader should avoid that.

Can you confirm that? Do you think that this config would be appropriate for production:

$config = new \ProxyManager\Configuration();

$config->setProxiesTargetDir(__DIR__ . '/my/generated/classes/cache/dir');
$config->setAutoGenerateProxies(true);
spl_autoload_register($config->getProxyAutoloader());

Thanks!

Disambiguate "generate" and "Generator".

Hi,
walking through the library, there are some things that jumped to my attention.
I don't know if this can ever be fixed (without breaking bc compatibility), but I mention it nevertheless. Maybe for the next major version.
I also don't know if these are common terms in the class generation business.

The terms "generate" and "Generator" are used a lot in the library.
For different things:

  • ProxyGeneratorInterface::generate() means to apply modifications to a class definition, before the actual php code is generated.
  • GeneratorStrategyInterface::generate() means to generate a php string from the class definition, potentially write it to a file, and execute it (require/eval).
  • (Zend)ClassGenerator represents a class definition that can be modified.
  • ProxyGenerator(Interface) is an object that can apply modifications to to a class definition (represented by ClassGenerator)).
  • GeneratorStrategy(Interface) is an object that can turn a class definition (represented by ClassGenerator)) into a PHP string.

This does not make the code harder to read than it needs to be.
Imo, those classes and methods should be named to express what they actually do, and to make them clearly distinguishable from other components. Some metaphors might help.

I am dropping some names here, which are horribly awkward, but at least they are expressive and distinguishable. It ain't easy!

To me, "generate" means the entire process of generating the class. So the individual steps should have different names.

ClassGenerator -> ClassToBeCreated, ClassDraft, ClassOutline, ClassBlueprint
(probably can't change this since it comes from Zend)

ProxyGenerator(Interface) -> ProxyClassPreparator, ProxyClassEnhancer, ProxyEngineer, ProxyPlanner, ProxyComposer, ProxyClassShaper
(because it does not generate the actual class, but add methods and stuff to a ClassBlueprint.)

GeneratorStrategy(Interface) -> CompletionStrategy, SummonStrategy, PHPStrategy, ExecutionStrategy
(indicating that this is the last step)

Hope this does make sense :)

Proxying Core PHP Classes

It seems that it's currently not possible to proxy a core PHP class like \SoapClient.

When I attempt to do this, the call to Zend\Code\Reflection\MethodReflection::getFilename() ends up returning false within the call to getContents(). In the documentation for this method, it states that core PHP classes will always return false.

This leads to my next question: Will this functionality ever be possible? It would be ideal if I could make many of my SoapClient services lazy, since I'd then be able to catch connection exceptions on construct and wouldn't have the overhead of connections that aren't used.

Any help or advice is appreciated. Thanks!

Missing proxy type: Lazy Reference

A lazy reference is a weak reference to an object, based on either a registry or some memory/performance aware data structure.

Typical use cases are swapping references to objects at runtime or garbage-collecting unused instances with Weakref.

Ghost Proxy: Constructor of base object

Trying to integrate GhostProxies into our ORM, we need the base object's constructor to be called before "createProxy"'s closure being executed, as this constructor prepares the object for operations we want to perform within the "createProxy" closure.

How do we do that? Due to lacking access to the base object within the closure we also cannot call the constructor explicitly (which would be quite dirty anyway IMO).

Container proxy?

Hi!
I am not sure this fits here..

On a Drupal module I maintain, I introduced a mini-DIC, that is, a container with a factory.

For services:
https://github.com/donquixote/drupal-crumbs/blob/7.x-2.x/lib/Container/LazyServices.php
https://github.com/donquixote/drupal-crumbs/blob/7.x-2.x/lib/ServiceFactory.php

For data:
https://github.com/donquixote/drupal-crumbs/blob/7.x-2.x/lib/Container/LazyData.php
https://github.com/donquixote/drupal-crumbs/blob/7.x-2.x/lib/CurrentPageInfo.php

This works quite ok. However, what I miss is the type hints from the IDE.
E.g. when working with the service container, I write stuff like this:

$services->pluginEngine->doSomething();

The IDE cannot determine the type of $services->pluginEngine.

I think this is a general thing with any DIC.
$container->get('myService') in Symfony will also prevent the IDE from helping you.

So.. I was wondering, could there be a way to generate a kind of DIC with type hints?

The generated code could look like this:

class CompiledContainer {
  protected $cachedServices = array();
  protected $factory;

  /**
   * @return MyServiceClass
   */
  function get_myService() {
    if (!isset($this->cachedServices['myService'])) {
      $this->cachedServices['myService'] = $this->factory->myService($this);
    }
    return $this->cachedServices['myService'];
  }

  /**
   * @return MyOtherServiceClass
   */
  function get_myOtherService() {
    if (!isset($this->cachedServices['myOtherService'])) {
      $this->cachedServices['myOtherService'] = $this->factory->myOtherService($this);
    }
    return $this->cachedServices['myOtherService'];
  }
}

If we are dealing with a simple factory, the type hints could be taken from there.

This somehow feels related to proxies, but I am not sure how far.

Refactor functional tests to use the factories

Currently, all functional tests emulate the fuctionality built in factories - this is very error prone and should instead use the factories directly to check as many paths as possible.

Missing proxy type: Value holder with partial overrides

A value holder with partial method overrides needs to localize scope of the proxied object:

class Foo
{
    protected $bar;
}
class FooProxy
{
    public function __construct(Foo $original)
    {
        $this->bar = & $original->bar;
    }
}

This allows to override the original class methods by still keeping a reference to the original object. Scope is shared, and the performance impact is reduced by quite a bit.

This was used in OcraServiceManager to decorate (and swap out) already existing instances of the service manager. By doing so, we avoid any troubles with assignments already done (internally) by the service manager itself.

Proxying protected methods

Currently, the library only handles proxying of public properties.

It could be interesting to consider proxying also protected methods, but that's also (obviously) a violation of good OOP, since we then start doing assumptions on the internal API of an object.

It can be useful for debugging purposes though.

Changes required to make this happen are minimal though.

Object Prototypes - extending objects at runtime

Using the access interceptor logic (or similar logic) it should be quite easy to implement object prototypes.

An example DSL for object prototypes in PHP would be like following:

class Foo
{
    public function doFoo()
    {
        return 'foo';
    }

    public function doBar()
    {
        return 'bar';
    }
}

$object = $prototypeApplier->applyPrototype(
    new Foo(),
    array(
        'doFoo' => function () { return 'baz'; },
        'addedMethod' => function () { return 'new method'; }
    )
);

echo $object->doFoo() . "\n"; // 'baz'
echo $object->doBar() . "\n"; // 'bar'
echo $object->addedMethod() . "\n"; // 'new method'

Additionally, a prototype may also implement some additional interfaces on the wrapper at runtime, which probably calls for a PrototypeDefinition class or builder. This can be useful when we want to extend an API and still keep type-safety:

PHP 5.3 could be supported via code generation and something like super_closure, but I'm not sure if it's worth it

Alternative approach - feedback?

Hi Marco,
coming from some discussions on Drupal 8 core development, I came up with my own idea of a "proxy class". I was then pointed here. Now I am trying to figure out how we are different, and why we would do it one or the other way. I imagine you could have some valuable feedback.
http://drupal.org/node/1973618 (code is over there)

The main purpose of a proxy object in Drupal 8 would be with a DIC, where a service X can have a proxy of Y injected, instead of the real thing, so Y is not instantiated before it is actually used. So far, we are on the same page. I think.

There are some major differences, though. (And these are from my current level of understanding of ProxyManager. Please correct me where I'm wrong.)

ProxyManager:

  1. A proxy is meant to be indistinguishable from the original object.
  2. There will be one seperate proxy class per proxied service, to mimic the complete signature. (I hope I am correct on this one)
  3. The consumer does not know whether it has a proxy or the real thing.
  4. A service X that receives a proxy is meant to use the proxy instead of the real thing, throughout the lifetime of X, resulting in an indirection overhead each time the proxy is used.
  5. The $container->get($key) either always returns a proxy for service Y, or it always returns the real Y.
  6. $container->getDefinitions('my_super_slow_class')->setLazy(true); tells the DIC to always return a proxy instead of the real thing.
  7. It is a damn huge library.

LazyService from http://drupal.org/node/1973618#comment-7316212:

  1. The lazy service behaves mostly like the original, but only via magic methods. It is easily exposed by method_exists(), get_class() etc. Also it has a method "bindReference()" which is likely not present in the wrapped object (and if it was, it would probably have a different purpose).
  2. There is only one LazyService class. All lazy service wrappers are from the same class.
  3. The consumer knows that it receives a proxy object instead of the real thing. It even expects that.
  4. The consumer's variable that holds the proxy, will be replaced with the real thing once the instantiation triggers. This way, we avoid the indirection overhead most of the time when the depended-on service is used.
    E.g. if the consumer does $this->someLazyService->foo() ten times, then only the first of these will have the indirection overhead. The other nine will work directly on the real thing.
  5. The $container->get($key) always returns the real thing. We'd have a $container->getProxy($key) to return a proxy instead.
  6. A new mechanic in services.yml (yet to be invented) tells the DIC that a specific service X wants a proxy of Z, instead of the real Z.
    At the same time, another service Y might want the real Z, and not a proxy.
  7. It is done with a pretty small amount of code.

Are these observations correct?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.