[DDC-717] Do not use files when using proxy autogeneration Created: 22/Jul/10  Updated: 20/Aug/13  Resolved: 20/Aug/13

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: None
Fix Version/s: 2.4
Security Level: All

Type: Improvement Priority: Major
Reporter: Jaka Jancar Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 2
Labels: None


 Description   

Proxy classes are generated in less than 1ms for me. I prefer to not have a "build" step to reducing loading time by a milisecond, so I use autogenerate.

For users like me, wouldn't it be nicer if we wouldn't even have to configure a proxy dir and those files were never written (since they're not read more than once anyway)?



 Comments   
Comment by Jaka Jancar [ 22/Jul/10 ]

This very minimal patch removes the use of these temporary files:

--- library/Doctrine/ORM/Proxy/ProxyFactory.php	(revision 2)
+++ library/Doctrine/ORM/Proxy/ProxyFactory.php	(working copy)
@@ -78,9 +78,8 @@
         $fqn = $this->_proxyNamespace . '\\' . $proxyClassName;
 
         if ($this->_autoGenerate && ! class_exists($fqn, false)) {
-            $fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php';
-            $this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate);
-            require $fileName;
+            $file = $this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, null, self::$_proxyClassTemplate);
+            eval('?>'.$file);
         }
 
         if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) {
@@ -144,6 +143,9 @@
 
         $file = str_replace($placeholders, $replacements, $file);
 
+        if ($fileName === null)
+            return $file;
+
         file_put_contents($fileName, $file);
     }
 
Comment by Benjamin Eberlei [ 23/Jul/10 ]

The proxy dir is used for the "doctrine orm:generate-proxies" command in the case of "autogenerate= false", so you need to define it anyways.

You have to use proxies, the option is not for Proxy yes/no. If you have autogenerate=false and doctrine requires a proxy for a use case but can't find it you will get a fatal error.

Comment by Benjamin Eberlei [ 23/Jul/10 ]

I just saw the eval() keyword, ieeks It could maybe be a convenience option for those that don't want to use proxy direcotiries, however i am not sure.

Comment by Jaka Jancar [ 23/Jul/10 ]

eval() is no different than writing code to a file and using require().

When using runtime-generated proxies, there are no benefits (that I know of) from writing them to a file. The disadvantages are:

  • slower because of write disk access
  • has problems with high concurrency, unless special care is taken (DDC-716)
  • potentially has permission problems if code is executed by different users (e.g. nobody for a daemon and www-data for http)
  • requires setup of a writable directory

This is a nicer patch, which makes _generateProxyClass() return a string, just like other _generate* methods:

--- library/Doctrine/ORM/Proxy/ProxyFactory.php	(revision 2)
+++ library/Doctrine/ORM/Proxy/ProxyFactory.php	(working copy)
@@ -78,9 +78,8 @@
         $fqn = $this->_proxyNamespace . '\\' . $proxyClassName;
 
         if ($this->_autoGenerate && ! class_exists($fqn, false)) {
-            $fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php';
-            $this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate);
-            require $fileName;
+            $code = $this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName);
+            eval($code);
         }
 
         if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) {
@@ -107,19 +106,19 @@
         foreach ($classes as $class) {
             $proxyClassName = str_replace('\\', '', $class->name) . 'Proxy';
             $proxyFileName = $proxyDir . $proxyClassName . '.php';
-            $this->_generateProxyClass($class, $proxyClassName, $proxyFileName, self::$_proxyClassTemplate);
+            $code = $this->_generateProxyClass($class, $proxyClassName);
+            file_put_contents($proxyFileName, "<?php\n" . $code);
         }
     }
 
     /**
      * Generates a proxy class file.
      *
-     * @param $class
-     * @param $originalClassName
+     * @param ClassMetadata $class
      * @param $proxyClassName
-     * @param $file The path of the file to write to.
+     * @return string The code of the generated methods.
      */
-    private function _generateProxyClass($class, $proxyClassName, $fileName, $file)
+    private function _generateProxyClass(ClassMetadata $class, $proxyClassName)
     {
         $methods = $this->_generateMethods($class);
         $sleepImpl = $this->_generateSleep($class);
@@ -142,9 +141,9 @@
             $methods, $sleepImpl
         );
 
-        $file = str_replace($placeholders, $replacements, $file);
+        $file = str_replace($placeholders, $replacements, self::$_proxyClassTemplate);
 
-        file_put_contents($fileName, $file);
+        return $file;
     }
 
     /**
@@ -244,8 +243,7 @@
 
     /** Proxy class code template */
     private static $_proxyClassTemplate =
-'<?php
-
+'
 namespace <namespace>;
 
 /**
Comment by Benjamin Eberlei [ 24/Jul/10 ]

Scheduled usage of eval() for 2.1, if the following conditions exist:

1. Autogenerate is set to TRUE
2. No Proxy Directory is configured.

Comment by Jaka Jancar [ 24/Jul/10 ]

Great, this is even better. This way you can have 1) autogenerated in ram-only, 2) autogenerated in files and 3) pregenerated.

And the minimal amount of config needed to get up and running is reduced, which is always nice.

Comment by Benjamin Eberlei [ 24/Jul/10 ]

you should know though, eval is dead slow. It generates the necessary proxies on EACH request and that cannot be cached in APC.

Comment by Jaka Jancar [ 24/Jul/10 ]

It's no slower than current autogeneration (file_put_contents+require). TBH, I don't know why anyone would want to use that over eval(), but I don't mind it being there.

Pre-generation is, of course, a different thing. Seems like a valid tradeoff to offer: build/a bit of config/better perfomance vs. no build/no config/potentially slower.

Comment by Benjamin Eberlei [ 24/Jul/10 ]

Yes, that is because file_put_contents + require is a development only strategy. The manual clearly states that autogenerate has to be false in production.

Comment by Roman S. Borschel [ 12/Aug/10 ]

Using eval() instead of producing and requiring the file in the case of enabled auto-generation of proxy classes sounds like a good improvement for 2.1 to make proxies more transparent during deveopment and for anyone for whom performance is no issue.

I'm increasing the priority as I think it is easy to implement for 2.1 and a good enhancement.

Comment by Karsten Dambekalns [ 09/Feb/11 ]

A note on why having the proxies written to a file can be useful even with autogenerate being on: it makes it really easy to check the proxy code being generated. I use that a lot currently.

The solution suggested, giving three possibilities is cool, though.

Generated at Sat Aug 23 19:50:00 UTC 2014 using JIRA 6.2.3#6260-sha1:63ef1d6dac3f4f4d7db4c1effd405ba38ccdc558.