Skip to content

[DCOM-96] ProxyFactory logic moved to doctrine common #168

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

Merged
merged 1 commit into from
Jan 10, 2013

Conversation

Ocramius
Copy link
Member

Initial requirements

  1. Common proxy generation for:
  2. Generic implementation of proxies: it is now possible to proxy any object as long as a ClassMetadata definition is provided
  3. Public properties lazy loading (yes we can!)

Sample Proxy

See this example generated proxy class

Public properties lazy loading

Public properties lazy loading has been implemented, and I've opened php/php-src#228 to protect the trick discovered by @lsmith77.

Mapped public fields and associations will trigger lazy loading through __get, __set and __isset.

Transient properties and identifier fields won't trigger lazy loading. Same applies for simple identifier getters (as was already happening in ORM since 2.2). I've used the ClassMetadata interface to determine fields and associations to lazy load.

New Proxy interface (not a BC Break)

interface Proxy
{
    const MARKER = '__CG__';
    const MARKER_LENGTH = 6;
    public function __load();
    public function __isInitialized();
    public function __setInitialized($initialized);
    public function __setInitializer(Closure $initializer = null);
    public function __getInitializer();
    public function __setCloner(Closure $cloner = null);
    public function __getCloner();
    public function __getLazyProperties(); // retrieves default properties values - useful when restoring properties
}

Lazy loading via closures

I came up with the idea of using closures to lazy load proxies. This allows removal of any reference to the OjectManager, and makes it possible to dump Proxy objects as long as there are not circular references between them (XDebug solves this).

The default constructor as of the current proxy generator implementation has following signature:

public function __construct(Closure $initializer = null, Closure $cloner = null);

When used by the Proxy, the initializer will be called with object itself as first parameter, the called method name (the one that triggered lazy loading) as second parameter and the parameters that were passed to that method as third parameter.

This allows any of the current implementation to define an initialization logic (even outside the scope of persistence), and to eventually define some finer grained logic to be dispatched for LOB fields or getters/setters.

Proxy generation

Generating a proxy requires that a valid instance of Doctrine\Common\Persistence\Mapping\ClassMetadata is passed to the Doctrine\Common\Proxy\ProxyGenerator. A proxy namespace and a proxy directory are also required to build the ProxyGenerator itself.

The generator uses a template and a list of placeholders mapped to either strings or callables. Those placeholders are used in the template while generating the Proxy class. Every callable mapped as a placeholder is called with the class metadata as first parameter. This should allow some fine-grained customization when the defaults of the proxy generator are not enough.

Autoloading

Autoloader for proxies has been simply copied from the ORM.

Testing

I tested each case that I could come up with. I need suggestions on edge cases that should be documented (like above, when I mentioned setting public properties and the PHP bug).

Directions for MongoDB ODM/PHPCR implementors

I've opened following related PRs:

Performance

  • When having identifier fields being private or protected, the proxy factory is twice as fast in instantiating a proxy compared to before (proxy factory to be seen in [DCOM-96] Moving proxy generation and autoloading to common orm#406)
  • When having identifier fields being public, the proxy factory is twice as slow in instantiating a proxy compared to before. Hydration is also slower on public properties. This can be fixed by excluding the identifier fields from being associated to a Doctrine\Common\Reflection\RuntimePublicReflectionProperty, which is a workaround for PHP Bug 63463. It could also be possible to speed up hydration of proxies by using bulk reflection operation.

That said, allover performance and memory usage is kept almost at same levels (sometimes even faster), but the new feature is introduced.

@schmittjoh
Copy link
Member

Just curious, have you thought about using my cg-library? This should allow you to save a lot of code.

I'm using it for generating all sorts of proxies in Symfony2.

@Ocramius
Copy link
Member Author

@schmittjoh never tried it, but I don't know if we can include it here as a dependency...

@stof
Copy link
Member

stof commented Jul 21, 2012

@beberlei's goal is to replace the code generation in Doctrine to use https://github.com/beberlei/DoctrineCodeGenerator so it could probably be used for proxies too

* @static
* @return self
*/
public static function proxyDirectoryRequired() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CS issue :)

@Ocramius
Copy link
Member Author

@stof not sure it should be achieved immediately, but yes, that would be a nice one :)

* @param $class
* @return string
*/
private function _generateSleep(ClassMetadata $class)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove the _. It was part of the doctrine1 CS (used for the early development of doctrine2 too) but not of the current ones anymore (new code does not use it anymore, and private methods are renamed when the code is modified in existing classes.

@stof
Copy link
Member

stof commented Jul 22, 2012

@Ocramius my link to the CodeGenerator was mainly a reply to the CG suggestion

@lsmith77
Copy link
Member

like i said on IRC yesterday .. i would really like to retain the ability to map public properties:
https://github.com/doctrine/phpcr-odm/blob/master/lib/Doctrine/ODM/PHPCR/Proxy/ProxyFactory.php#L366

@beberlei
Copy link
Member

Onl

Am 22.07.2012 um 02:00 schrieb Christophe [email protected]:

@beberlei's goal is to replace the code generation in Doctrine to use https://github.com/beberlei/DoctrineCodeGenerator so it could probably be used for proxies too


Reply to this email directly or view it on GitHub:
#168 (comment)

@beberlei
Copy link
Member

Only userland code generation, not our own.

Am 22.07.2012 um 02:00 schrieb Christophe [email protected]:

@beberlei's goal is to replace the code generation in Doctrine to use https://github.com/beberlei/DoctrineCodeGenerator so it could probably be used for proxies too


Reply to this email directly or view it on GitHub:
#168 (comment)

@Ocramius
Copy link
Member Author

@lsmith77 I am quite against public properties myself in my entities. Having __set (and it's siblings in the PHP docs) implemented by default would be wrong in my use case. Anyway, I'm thinking of allowing to override the template set in the generator, which would add a nice degree of flexibility.
Also it is quite unclear how this public properties mapping would be achieved, but I'm pinging you on IRC for that :)


$className = str_replace('\\', '', substr($className, strlen($proxyNamespace) +1));

return $proxyDir . DIRECTORY_SEPARATOR . $className.'.php';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around the last .

@Ocramius
Copy link
Member Author

Ocramius commented Jan 6, 2013

@guilhermeblanco could you check this one and the related PRs so that I can remove all the references to my forks? :)


if ($this->isShortIdentifierGetter($method, $class)) {
$identifier = lcfirst(substr($name, 3));
$cast = in_array($class->getTypeOfField($identifier), array('integer', 'smallint')) ? '(int) ' : '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Align = signs

@guilhermeblanco
Copy link
Member

I added a few comments at the end.
Please update the remaining elements and I'm fine with merge! =)

@Ocramius
Copy link
Member Author

@guilhermeblanco merge :shipit:

@cordoval
Copy link

:rage4:

beberlei added a commit that referenced this pull request Jan 10, 2013
[DCOM-96] ProxyFactory logic moved to doctrine common
@beberlei beberlei merged commit d5843a4 into doctrine:master Jan 10, 2013
Ocramius added a commit to Ocramius/doctrine2 that referenced this pull request Feb 14, 2013
Ocramius added a commit to Ocramius/mongodb-odm that referenced this pull request Mar 4, 2013
Ocramius added a commit to Ocramius/phpcr-odm that referenced this pull request May 26, 2013
dbu added a commit to doctrine/phpcr-odm that referenced this pull request May 26, 2013
DCOM-96 compliance - proxy generation as of doctrine/common#168
Ocramius added a commit to Ocramius/mongodb-odm that referenced this pull request Feb 15, 2014
Ocramius added a commit to Ocramius/mongodb-odm that referenced this pull request Apr 1, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants