From cb1c7bce48a24db6b6efebaba93bdc59c40ff34f Mon Sep 17 00:00:00 2001 From: romanb Date: Wed, 14 Oct 2009 20:18:36 +0000 Subject: [PATCH] [2.0] DBAL cleanups (DDC-46). Proxy class improvements (DDC-19, DDC-39). Started new UPGRADE_TO document for upgrade instructions between advancing Doctrine 2 versions. --- UPGRADE_TO_2_0 | 55 ++++ lib/Doctrine/Common/GlobalClassLoader.php | 11 +- lib/Doctrine/Common/IsolatedClassLoader.php | 4 +- .../DBAL/Schema/MySqlSchemaManager.php | 92 +++--- .../DBAL/Schema/OracleSchemaManager.php | 29 +- .../DBAL/Schema/PostgreSqlSchemaManager.php | 36 +- .../DBAL/Schema/SqliteSchemaManager.php | 33 +- lib/Doctrine/ORM/Configuration.php | 76 ++++- lib/Doctrine/ORM/EntityManager.php | 18 +- lib/Doctrine/ORM/Mapping/OneToOneMapping.php | 3 +- .../ORM/Proxy/ProxyClassGenerator.php | 284 ---------------- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 309 ++++++++++++++++-- lib/Doctrine/ORM/Tools/Cli.php | 1 + .../Tools/Cli/Tasks/GenerateProxiesTask.php | 110 +++++++ lib/Doctrine/ORM/UnitOfWork.php | 2 - .../Schema/MySqlSchemaManagerTest.php | 2 +- .../Schema/PostgreSqlSchemaManagerTest.php | 4 +- .../Schema/SqliteSchemaManagerTest.php | 4 +- .../Tests/Mocks/EntityManagerMock.php | 3 + .../OneToOneBidirectionalAssociationTest.php | 1 + .../ORM/Functional/ReferenceProxyTest.php | 6 +- .../ORM/Mapping/ClassMetadataFactoryTest.php | 2 + .../ORM/Proxy/ProxyClassGeneratorTest.php | 127 +++---- .../Doctrine/Tests/OrmFunctionalTestCase.php | 2 + tests/Doctrine/Tests/OrmTestCase.php | 2 + tools/sandbox/cli-config.php | 6 + tools/sandbox/index.php | 28 +- 27 files changed, 692 insertions(+), 558 deletions(-) create mode 100644 UPGRADE_TO_2_0 delete mode 100644 lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php create mode 100644 lib/Doctrine/ORM/Tools/Cli/Tasks/GenerateProxiesTask.php diff --git a/UPGRADE_TO_2_0 b/UPGRADE_TO_2_0 new file mode 100644 index 000000000..dca3f2be3 --- /dev/null +++ b/UPGRADE_TO_2_0 @@ -0,0 +1,55 @@ +> **NOTE** +> This document does not describe how to upgrade from Doctrine 1.x to Doctrine 2 as this is a more +> complicated process. + +# Upgrade from 2.0-ALPHA2 to 2.0-ALPHA3 + +This section details the changes made to Doctrine 2.0-ALPHA3 to make it easier for you +to upgrade your projects to use this version. + +## Class loader changes + +The behavior of the two class loaders in the Common package changed slightly for more flexibility. When registering a namespace you now need to include the last namespace separator as well, if you really want the prefix to be checked as a namespace and not only as the string. Examples: + + [php] + // This loader would load classes in the Doctrine namespace but not classes in other + // namespaces that start with the string "Doctrine", like "DoctrineExtensions". + $isolatedLoader = new IsolatedClassLoader('Doctrine\\'); + + // or... + $globalLoader = new GlobalClassLoader; + $globalLoader->registerNamespace('Doctrine\\', '/path/to/doctrine'); + + +## Proxy class changes + +You are now required to make supply some minimalist configuration with regards to proxy objects. That involves 2 new configuration options. First, the directory where generated proxy classes should be placed needs to be specified. Secondly, you need to configure the namespace used for proxy classes. The following snippet shows an example: + + [php] + // step 1: configure directory for proxy classes + // $config instanceof Doctrine\ORM\Configuration + $config->setProxyDir('/path/to/myproject/lib/MyProject/Generated/Proxies'); + $config->setProxyNamespace('MyProject\Generated\Proxies'); + +Note that proxy classes behave exactly like any other classes when it comes to class loading. Therefore you need to make sure the proxy classes can be loaded by some class loader. If you place the generated proxy classes in a namespace and directory under your projects class files, like in the example above, it would be sufficient to register the MyProject namespace on a class loader. Since the proxy classes are contained in that namespace and adhere to the standards for class loading, no additional work is required. +Generating the proxy classes into a namespace within your class library is the recommended setup. + +Entities with initialized proxy objects can now be serialized and unserialized properly from within the same application. + +For more details refer to the Configuration section of the manual. + +## Removed allowPartialObjects configuration option + +The allowPartialObjects configuration option together with the `Configuration#getAllowPartialObjects` and `Configuration#setAllowPartialObjects` methods have been removed. +The new behavior is as if the option were set to FALSE all the time, basically disallowing partial objects globally. However, you can still use the `Query::HINT_FORCE_PARTIAL_LOAD` query hint to force a query to return partial objects for optimization purposes. + +## Renamed Methods + +* Doctrine\ORM\Configuration#getCacheDir() to getProxyDir() +* Doctrine\ORM\Configuration#setCacheDir($dir) to setProxyDir($dir) + +# Upgrade from 2.0-ALPHA3 to 2.0-ALPHA4 + +- + + diff --git a/lib/Doctrine/Common/GlobalClassLoader.php b/lib/Doctrine/Common/GlobalClassLoader.php index efa4e7522..2c76f9d81 100644 --- a/lib/Doctrine/Common/GlobalClassLoader.php +++ b/lib/Doctrine/Common/GlobalClassLoader.php @@ -119,18 +119,13 @@ class GlobalClassLoader if (($pos = strpos($className, $separator)) !== false) { $prefix = substr($className, 0, strpos($className, $separator)); } else if (($pos = strpos($className, '_')) !== false) { - // Support for '_' namespace separator for compatibility with Zend/PEAR/... + // Support for '_' namespace separator for compatibility with the "old" standard $prefix = substr($className, 0, strpos($className, '_')); $separator = '_'; } - // Build the class file name - $class = ((isset($this->_basePaths[$prefix])) ? - $this->_basePaths[$prefix] . DIRECTORY_SEPARATOR : '') + require ((isset($this->_basePaths[$prefix])) ? $this->_basePaths[$prefix] . DIRECTORY_SEPARATOR : '') . str_replace($separator, DIRECTORY_SEPARATOR, $className) - . (isset($this->_fileExtensions[$prefix]) ? - $this->_fileExtensions[$prefix] : $this->_defaultFileExtension); - - require $class; + . (isset($this->_fileExtensions[$prefix]) ? $this->_fileExtensions[$prefix] : $this->_defaultFileExtension); } } \ No newline at end of file diff --git a/lib/Doctrine/Common/IsolatedClassLoader.php b/lib/Doctrine/Common/IsolatedClassLoader.php index fd5098f4c..c3654630e 100644 --- a/lib/Doctrine/Common/IsolatedClassLoader.php +++ b/lib/Doctrine/Common/IsolatedClassLoader.php @@ -104,11 +104,9 @@ class IsolatedClassLoader return false; } - // Build the class file name - $class = ($this->_basePath !== null ? $this->_basePath . DIRECTORY_SEPARATOR : '') + require ($this->_basePath !== null ? $this->_basePath . DIRECTORY_SEPARATOR : '') . str_replace($this->_namespaceSeparator, DIRECTORY_SEPARATOR, $className) . $this->_fileExtension; - require $class; return true; } diff --git a/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php b/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php index 34862550b..078303b28 100644 --- a/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php @@ -22,11 +22,12 @@ namespace Doctrine\DBAL\Schema; /** - * xxx + * Schema manager for the MySql RDBMS. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Konsta Vesterinen * @author Lukas Smith (PEAR MDB2 library) + * @author Roman Borschel * @version $Revision$ * @since 2.0 */ @@ -85,7 +86,15 @@ class MySqlSchemaManager extends AbstractSchemaManager { return $database['Database']; } - + + /** + * Gets a portable column definition. + * + * The database type is mapped to a corresponding Doctrine mapping type. + * + * @param $tableColumn + * @return array + */ protected function _getPortableTableColumnDefinition($tableColumn) { $dbType = strtolower($tableColumn['Type']); @@ -110,42 +119,36 @@ class MySqlSchemaManager extends AbstractSchemaManager $values = null; $scale = null; + // Map db type to Doctrine mapping type switch ($dbType) { case 'tinyint': - $type = 'integer'; $type = 'boolean'; - if (preg_match('/^(is|has)/', $tableColumn['name'])) { - $type = array_reverse($type); - } - $unsigned = preg_match('/ unsigned/i', $tableColumn['Type']); - $length = 1; - break; + $length = null; + break; case 'smallint': - $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['Type']); - $length = 2; - break; + $type = 'smallint'; + $length = null; + break; case 'mediumint': $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['Type']); - $length = 3; - break; + $length = null; + break; case 'int': case 'integer': $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['Type']); - $length = 4; - break; + $length = null; + break; case 'bigint': - $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['Type']); - $length = 8; - break; + $type = 'bigint'; + $length = null; + break; case 'tinytext': case 'mediumtext': case 'longtext': case 'text': - case 'text': + $type = 'text'; + $fixed = false; + break; case 'varchar': $fixed = false; case 'string': @@ -156,8 +159,8 @@ class MySqlSchemaManager extends AbstractSchemaManager if (preg_match('/^(is|has)/', $tableColumn['name'])) { $type = array_reverse($type); } - } elseif (strstr($dbType, 'text')) { - $type = 'clob'; + } else if (strstr($dbType, 'text')) { + $type = 'text'; if ($decimal == 'binary') { $type = 'blob'; } @@ -165,40 +168,28 @@ class MySqlSchemaManager extends AbstractSchemaManager if ($fixed !== false) { $fixed = true; } - break; + break; case 'set': $fixed = false; $type = 'text'; - $type = 'integer'; - break; + $type = 'integer'; //FIXME:??? + break; case 'date': $type = 'date'; - $length = null; - break; + break; case 'datetime': case 'timestamp': - $type = 'timestamp'; - $length = null; - break; + $type = 'datetime'; + break; case 'time': $type = 'time'; - $length = null; - break; + break; case 'float': case 'double': case 'real': - $type = 'float'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); - break; - case 'unknown': - case 'decimal': - if ($decimal !== null) { - $scale = $decimal; - } case 'numeric': $type = 'decimal'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); - break; + break; case 'tinyblob': case 'mediumblob': case 'longblob': @@ -207,15 +198,12 @@ class MySqlSchemaManager extends AbstractSchemaManager case 'varbinary': $type = 'blob'; $length = null; - break; + break; case 'year': $type = 'integer'; $type = 'date'; $length = null; - break; - case 'bit': - $type = 'bit'; - break; + break; case 'geometry': case 'geometrycollection': case 'point': @@ -226,7 +214,7 @@ class MySqlSchemaManager extends AbstractSchemaManager case 'multipolygon': $type = 'blob'; $length = null; - break; + break; default: $type = 'string'; $length = null; diff --git a/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php b/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php index 5941e488c..d8a7c17a9 100644 --- a/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php @@ -79,13 +79,14 @@ class OracleSchemaManager extends AbstractSchemaManager switch ($dbType) { case 'integer': + case 'number': + $type = 'integer'; + $length = null; + break; case 'pls_integer': case 'binary_integer': - if ($length == '1' && preg_match('/^(is|has)/', $tableColumn['column_name'])) { - $type = 'boolean'; - } else { - $type = 'integer'; - } + $type = 'boolean'; + $length = null; break; case 'varchar': case 'varchar2': @@ -104,28 +105,18 @@ class OracleSchemaManager extends AbstractSchemaManager break; case 'date': case 'timestamp': - $type = 'timestamp'; + $type = 'datetime'; $length = null; break; case 'float': - $type = 'float'; - break; - case 'number': - if ( ! empty($tableColumn['data_scale'])) { - $type = 'decimal'; - } else { - if ($length == '1' && preg_match('/^(is|has)/', $tableColumn['column_name'])) { - $type = 'boolean'; - } else { - $type = 'integer'; - } - } + $type = 'decimal'; + $length = null; break; case 'long': $type = 'string'; case 'clob': case 'nclob': - $type = 'clob'; + $type = 'text'; break; case 'blob': case 'raw': diff --git a/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php b/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php index 6f09c13a2..81748021f 100644 --- a/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php @@ -103,7 +103,7 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager $tableColumn['length'] = $length; } - $matches = array(); + $matches = array(); if (preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) { $tableColumn['sequence'] = $matches[1]; @@ -122,7 +122,7 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager $length = null; } $type = array(); - $unsigned = $fixed = null; + $fixed = null; if ( ! isset($tableColumn['name'])) { $tableColumn['name'] = ''; @@ -131,23 +131,10 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager $dbType = strtolower($tableColumn['type']); switch ($dbType) { - case 'inet': - $type = 'inet'; - break; - case 'bit': - case 'varbit': - $type = 'bit'; - break; case 'smallint': case 'int2': - $type = 'integer'; - $unsigned = false; - $length = 2; - if ($length == '2') { - if (preg_match('/^(is|has)/', $tableColumn['name'])) { - $type = 'boolean'; - } - } + $type = 'smallint'; + $length = null; break; case 'int': case 'int4': @@ -155,21 +142,19 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager case 'serial': case 'serial4': $type = 'integer'; - $unsigned = false; - $length = 4; + $length = null; break; case 'bigint': case 'int8': case 'bigserial': case 'serial8': - $type = 'integer'; - $unsigned = false; - $length = 8; + $type = 'bigint'; + $length = null; break; case 'bool': case 'boolean': $type = 'boolean'; - $length = 1; + $length = null; break; case 'text': $fixed = false; @@ -203,7 +188,7 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager case 'timestamp': case 'timetz': case 'timestamptz': - $type = 'timestamp'; + $type = 'datetime'; $length = null; break; case 'time': @@ -216,8 +201,6 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager case 'double': case 'double precision': case 'real': - $type = 'float'; - break; case 'decimal': case 'money': case 'numeric': @@ -254,7 +237,6 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager $decl = array( 'type' => $type, 'length' => $length, - 'unsigned' => $unsigned, 'fixed' => $fixed ); diff --git a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php index e64282deb..9e0a07f76 100644 --- a/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php @@ -106,36 +106,24 @@ class SqliteSchemaManager extends AbstractSchemaManager $type = 'boolean'; break; case 'tinyint': - if (preg_match('/^(is|has)/', $tableColumn['name'])) { - $type = 'boolean'; - } else { - $type = 'integer'; - } - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); - $length = 1; + $type = 'boolean'; + $length = null; break; case 'smallint': - $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); - $length = 2; + $type = 'smallint'; + $length = null; break; case 'mediumint': - $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); - $length = 3; - break; case 'int': case 'integer': case 'serial': $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); - $length = 4; + $length = null; break; case 'bigint': case 'bigserial': - $type = 'integer'; - $unsigned = preg_match('/ unsigned/i', $tableColumn['type']); - $length = 8; + $type = 'bigint'; + $length = null; break; case 'clob': $fixed = false; @@ -145,6 +133,8 @@ class SqliteSchemaManager extends AbstractSchemaManager case 'mediumtext': case 'longtext': case 'text': + $type = 'text'; + break; case 'varchar': case 'varchar2': case 'nvarchar': @@ -172,7 +162,7 @@ class SqliteSchemaManager extends AbstractSchemaManager break; case 'datetime': case 'timestamp': - $type = 'timestamp'; + $type = 'datetime'; $length = null; break; case 'time': @@ -182,9 +172,6 @@ class SqliteSchemaManager extends AbstractSchemaManager case 'float': case 'double': case 'real': - $type = 'float'; - $length = null; - break; case 'decimal': case 'numeric': $type = 'decimal'; diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 007082ed6..371abd7e5 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -44,11 +44,13 @@ class Configuration extends \Doctrine\DBAL\Configuration 'queryCacheImpl' => null, 'metadataCacheImpl' => null, 'metadataDriverImpl' => null, - 'cacheDir' => null, - 'allowPartialObjects' => true, + 'proxyDir' => null, + 'allowPartialObjects' => true, //TODO: Remove 'useCExtension' => false, 'namedQueries' => array(), - 'namedNativeQueries' => array() + 'namedNativeQueries' => array(), + 'autoGenerateProxyClasses' => true, + 'proxyNamespace' => null )); //TODO: Move this to client code to avoid unnecessary work when a different metadata @@ -65,6 +67,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * and you always only get what you explicitly query for. * * @return boolean Whether partial objects are allowed. + * @todo Remove + * @deprecated */ public function getAllowPartialObjects() { @@ -78,6 +82,8 @@ class Configuration extends \Doctrine\DBAL\Configuration * and you always only get what you explicitly query for. * * @param boolean $allowed Whether partial objects are allowed. + * @todo Remove + * @deprecated */ public function setAllowPartialObjects($allowed) { @@ -85,23 +91,55 @@ class Configuration extends \Doctrine\DBAL\Configuration } /** - * Sets the directory where Doctrine writes any necessary cache files. + * Sets the directory where Doctrine generates any necessary proxy class files. * * @param string $dir */ - public function setCacheDir($dir) + public function setProxyDir($dir) { - $this->_attributes['cacheDir'] = $dir; + $this->_attributes['proxyDir'] = $dir; } /** - * Gets the directory where Doctrine writes any necessary cache files. + * Gets the directory where Doctrine generates any necessary proxy class files. * * @return string */ - public function getCacheDir() + public function getProxyDir() { - return $this->_attributes['cacheDir']; + return $this->_attributes['proxyDir']; + } + + /** + * Gets a boolean flag that indicates whether proxy classes should always be regenerated + * during each script execution. + * + * @return boolean + */ + public function getAutoGenerateProxyClasses() + { + return $this->_attributes['autoGenerateProxyClasses']; + } + + /** + * Sets a boolean flag that indicates whether proxy classes should always be regenerated + * during each script execution. + * + * @param boolean $bool + */ + public function setAutoGenerateProxyClasses($bool) + { + $this->_attributes['autoGenerateProxyClasses'] = $bool; + } + + public function getProxyNamespace() + { + return $this->_attributes['proxyNamespace']; + } + + public function setProxyNamespace($ns) + { + $this->_attributes['proxyNamespace'] = $ns; } /** @@ -251,4 +289,24 @@ class Configuration extends \Doctrine\DBAL\Configuration { return $this->_attributes['namedNativeQueries'][$name]; } + + /** + * Ensures that this Configuration instance contains settings that are + * suitable for a production environment. + * + * @throws DoctrineException If a configuration setting has a value that is not + * suitable for a production environment. + */ + public function ensureProductionSettings() + { + if ( ! $this->_attributes['queryCacheImpl']) { + throw DoctrineException::queryCacheNotConfigured(); + } + if ( ! $this->_attributes['metadataCacheImpl']) { + throw DoctrineException::metadataCacheNotConfigured(); + } + if ($this->_attributes['autoGenerateProxyClasses']) { + throw DoctrineException::proxyClassesAlwaysRegenerating(); + } + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 753e675cb..32698de0b 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -26,17 +26,16 @@ use Doctrine\Common\EventManager, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Mapping\ClassMetadataFactory, - Doctrine\ORM\Proxy\ProxyFactory, - Doctrine\ORM\Proxy\ProxyClassGenerator; + Doctrine\ORM\Proxy\ProxyFactory; /** * The EntityManager is the central access point to ORM functionality. * - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.doctrine-project.org - * @since 2.0 - * @version $Revision$ - * @author Roman Borschel + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.0 + * @version $Revision$ + * @author Roman Borschel */ class EntityManager { @@ -146,7 +145,10 @@ class EntityManager $this->_metadataFactory = new ClassMetadataFactory($this); $this->_metadataFactory->setCacheDriver($this->_config->getMetadataCacheImpl()); $this->_unitOfWork = new UnitOfWork($this); - $this->_proxyFactory = new ProxyFactory($this, new ProxyClassGenerator($this, $this->_config->getCacheDir())); + $this->_proxyFactory = new ProxyFactory($this, + $config->getProxyDir(), + $config->getProxyNamespace(), + $config->getAutoGenerateProxyClasses()); } /** diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php index 7b72af617..5de7e4a86 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -189,8 +189,7 @@ class OneToOneMapping extends AssociationMapping * @param object $sourceEntity the entity source of this association * @param object $targetEntity the entity to load data in * @param EntityManager $em - * @param array $joinColumnValues values of the join columns of $sourceEntity. There are no fields - * to store this data in $sourceEntity + * @param array $joinColumnValues Values of the join columns of $sourceEntity. */ public function load($sourceEntity, $targetEntity, $em, array $joinColumnValues = array()) { diff --git a/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php b/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php deleted file mode 100644 index 80b344694..000000000 --- a/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php +++ /dev/null @@ -1,284 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Proxy; - -use Doctrine\ORM\EntityManager, - Doctrine\ORM\Mapping\ClassMetadata; - -/** - * The ProxyClassGenerator is used to generate proxy objects for entities at runtime. - * - * @author Roman Borschel - * @author Giorgio Sironi - * @since 2.0 - */ -class ProxyClassGenerator -{ - /** The namespace for the generated proxy classes. */ - private static $_ns = 'Doctrine\Generated\Proxies\\'; - private $_cacheDir; - private $_em; - - /** - * Generates and stores proxy class files in the given cache directory. - * - * @param EntityManager $em - * @param string $cacheDir The directory where generated proxy classes will be saved. - * If not set, sys_get_temp_dir() is used. - */ - public function __construct(EntityManager $em, $cacheDir = null) - { - $this->_em = $em; - - if ($cacheDir === null) { - $cacheDir = sys_get_temp_dir(); - } - - $this->_cacheDir = rtrim($cacheDir, '/') . '/'; - } - - /** - * Generates a reference proxy class. - * This is a proxy for an object which we have the id for retrieval. - * - * @param string $originalClassName - * @return string name of the proxy class - */ - public function generateReferenceProxyClass($originalClassName) - { - $proxyClassName = str_replace('\\', '_', $originalClassName) . 'RProxy'; - - return $this->_generateClass($originalClassName, $proxyClassName, self::$_proxyClassTemplate); - } - - /** - * Generates an association proxy class. - * This is a proxy class for an object which we have the association where - * it is involved, but no primary key to retrieve it. - * - * @param string $originalClassName - * @return string the proxy class name - */ - public function generateAssociationProxyClass($originalClassName) - { - $proxyClassName = str_replace('\\', '_', $originalClassName) . 'AProxy'; - - return $this->_generateClass($originalClassName, $proxyClassName, self::$_assocProxyClassTemplate); - } - - private function _generateClass($originalClassName, $proxyClassName, $file) - { - $proxyFullyQualifiedClassName = self::$_ns . $proxyClassName; - - if ($this->_em->getMetadataFactory()->hasMetadataFor($proxyFullyQualifiedClassName)) { - return $proxyFullyQualifiedClassName; - } - - $class = $this->_em->getClassMetadata($originalClassName); - $this->_em->getMetadataFactory()->setMetadataFor($proxyFullyQualifiedClassName, $class); - - if (class_exists($proxyFullyQualifiedClassName, false)) { - return $proxyFullyQualifiedClassName; - } - - $fileName = $this->_cacheDir . $proxyClassName . '.g.php'; - - if (file_exists($fileName)) { - require $fileName; - return $proxyFullyQualifiedClassName; - } - - $methods = $this->_generateMethods($class); - $sleepImpl = $this->_generateSleep($class); - $constructorInv = $class->reflClass->hasMethod('__construct') ? 'parent::__construct();' : ''; - - $placeholders = array( - '', '', - '', '', - '' - ); - $replacements = array( - $proxyClassName, $originalClassName, - $methods, $sleepImpl, - $constructorInv - ); - - $file = str_replace($placeholders, $replacements, $file); - - file_put_contents($fileName, $file); - - require $fileName; - - return $proxyFullyQualifiedClassName; - } - - private function _generateMethods(ClassMetadata $class) - { - $methods = ''; - - foreach ($class->reflClass->getMethods() as $method) { - if ($method->isConstructor()) { - continue; - } - - if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) { - $methods .= PHP_EOL . 'public function ' . $method->getName() . '('; - $firstParam = true; - $parameterString = $argumentString = ''; - - foreach ($method->getParameters() as $param) { - if ($firstParam) { - $firstParam = false; - } else { - $parameterString .= ', '; - $argumentString .= ', '; - } - - // We need to pick the type hint class too - if (($paramClass = $param->getClass()) !== null) { - $parameterString .= '\\' . $paramClass->getName() . ' '; - } else if ($param->isArray()) { - $parameterString .= 'array '; - } - - if ($param->isPassedByReference()) { - $parameterString .= '&'; - } - - $parameterString .= '$' . $param->getName(); - $argumentString .= '$' . $param->getName(); - - if ($param->isDefaultValueAvailable()) { - $parameterString .= ' = ' . var_export($param->getDefaultValue(), true); - } - } - - $methods .= $parameterString . ') {' . PHP_EOL; - $methods .= '$this->_load();' . PHP_EOL; - $methods .= 'return parent::' . $method->getName() . '(' . $argumentString . ');'; - $methods .= '}' . PHP_EOL; - } - } - - return $methods; - } - - private function _generateSleep(ClassMetadata $class) - { - $sleepImpl = ''; - - if ($class->reflClass->hasMethod('__sleep')) { - $sleepImpl .= 'return parent::__sleep();'; - } else { - $sleepImpl .= 'return array('; - $first = true; - - foreach ($class->getReflectionProperties() as $name => $prop) { - if ($first) { - $first = false; - } else { - $sleepImpl .= ', '; - } - - $sleepImpl .= "'" . $name . "'"; - } - - $sleepImpl .= ');'; - } - - return $sleepImpl; - } - - - /** Proxy class code template */ - private static $_proxyClassTemplate = -' extends \ implements \Doctrine\ORM\Proxy\Proxy { - private $_entityPersister; - private $_identifier; - private $_loaded = false; - public function __construct($entityPersister, $identifier) { - $this->_entityPersister = $entityPersister; - $this->_identifier = $identifier; - - } - private function _load() { - if ( ! $this->_loaded) { - $this->_entityPersister->load($this->_identifier, $this); - unset($this->_entityPersister); - unset($this->_identifier); - $this->_loaded = true; - } - } - - - - public function __sleep() { - if (!$this->_loaded) { - throw new \RuntimeException("Not fully loaded proxy can not be serialized."); - } - - } - } -}'; - - private static $_assocProxyClassTemplate = -' extends \ implements \Doctrine\ORM\Proxy\Proxy { - private $_em; - private $_assoc; - private $_owner; - private $_joinColumnValues; - private $_loaded = false; - public function __construct($em, $assoc, $owner, array $joinColumnValues) { - $this->_em = $em; - $this->_assoc = $assoc; - $this->_owner = $owner; - $this->_joinColumnValues = $joinColumnValues; - - } - private function _load() { - if ( ! $this->_loaded) { - $this->_assoc->load($this->_owner, $this, $this->_em, $this->_joinColumnValues); - unset($this->_em); - unset($this->_owner); - unset($this->_assoc); - unset($this->_joinColumnValues); - $this->_loaded = true; - } - } - - - - public function __sleep() { - if (!$this->_loaded) { - throw new \RuntimeException("Not fully loaded proxy can not be serialized."); - } - - } - } -}'; -} diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index f01fac1f4..f69a21369 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -21,10 +21,13 @@ namespace Doctrine\ORM\Proxy; -use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManager, + Doctrine\ORM\Mapping\ClassMetadata, + Doctrine\ORM\Mapping\AssociationMapping, + Doctrine\Common\DoctrineException; /** - * This Factory is used to create proxy objects for entities at runtime. + * This factory is used to create proxy objects for entities at runtime. * * @author Roman Borschel * @author Giorgio Sironi @@ -32,25 +35,41 @@ use Doctrine\ORM\EntityManager; */ class ProxyFactory { + /** The EntityManager this factory is bound to. */ private $_em; - private $_generator; + /** Whether to automatically (re)generate proxy classes. */ + private $_autoGenerate; + /** The namespace that contains all proxy classes. */ + private $_proxyNamespace; + /** The directory that contains all proxy classes. */ + private $_proxyDir; /** - * Initializes a new instance of the DynamicProxyGenerator class that is - * connected to the given EntityManager and stores proxy class files in - * the given cache directory. + * Initializes a new instance of the ProxyFactory class that is + * connected to the given EntityManager. * - * @param EntityManager $em - * @param Generator $generator + * @param EntityManager $em The EntityManager the new factory works for. + * @param string $proxyDir The directory to use for the proxy classes. It must exist. + * @param string $proxyNs The namespace to use for the proxy classes. + * @param boolean $autoGenerate Whether to automatically generate proxy classes. */ - public function __construct(EntityManager $em, ProxyClassGenerator $generator) + public function __construct(EntityManager $em, $proxyDir, $proxyNs, $autoGenerate = false) { + if ( ! $proxyDir) { + throw DoctrineException::proxyDirectoryRequired(); + } + if ( ! $proxyNs) { + throw DoctrineException::proxyNamespaceRequired(); + } $this->_em = $em; - $this->_generator = $generator; - } - + $this->_proxyDir = $proxyDir; + $this->_autoGenerate = $autoGenerate; + $this->_proxyNamespace = $proxyNs; + } + /** - * Gets a reference proxy instance. + * Gets a reference proxy instance for the entity of the given type and identified by + * the given identifier. * * @param string $className * @param mixed $identifier @@ -58,17 +77,269 @@ class ProxyFactory */ public function getReferenceProxy($className, $identifier) { - $proxyClassName = $this->_generator->generateReferenceProxyClass($className); + $proxyClassName = str_replace('\\', '', $className) . 'RProxy'; + $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; + } + + if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) { + $this->_em->getMetadataFactory()->setMetadataFor($fqn, $this->_em->getClassMetadata($className)); + } + $entityPersister = $this->_em->getUnitOfWork()->getEntityPersister($className); - return new $proxyClassName($entityPersister, $identifier); + + return new $fqn($entityPersister, $identifier); } - + /** * Gets an association proxy instance. + * + * @param object $owner + * @param AssociationMapping $assoc + * @param array $joinColumnValues + * @return object */ - public function getAssociationProxy($owner, \Doctrine\ORM\Mapping\AssociationMapping $assoc, array $joinColumnValues) + public function getAssociationProxy($owner, AssociationMapping $assoc, array $joinColumnValues) { - $proxyClassName = $this->_generator->generateAssociationProxyClass($assoc->getTargetEntityName()); - return new $proxyClassName($this->_em, $assoc, $owner, $joinColumnValues); + $proxyClassName = str_replace('\\', '', $assoc->targetEntityName) . 'AProxy'; + $fqn = $this->_proxyNamespace . '\\' . $proxyClassName; + + if ($this->_autoGenerate && ! class_exists($fqn, false)) { + $fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php'; + $this->_generateProxyClass($this->_em->getClassMetadata($assoc->targetEntityName), $proxyClassName, $fileName, self::$_assocProxyClassTemplate); + require $fileName; + } + + if ( ! $this->_em->getMetadataFactory()->hasMetadataFor($fqn)) { + $this->_em->getMetadataFactory()->setMetadataFor($fqn, $this->_em->getClassMetadata($assoc->targetEntityName)); + } + + return new $fqn($this->_em, $assoc, $owner, $joinColumnValues); } + + /** + * Generates proxy classes for all given classes. + * + * @param array $classes The classes (ClassMetadata instances) for which to generate proxies. + * @param string $toDir The target directory of the proxy classes. If not specified, the + * directory configured on the Configuration of the EntityManager used + * by this factory is used. + */ + public function generateProxyClasses(array $classes, $toDir = null) + { + $proxyDir = $toDir ?: $this->_proxyDir; + $proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + foreach ($classes as $class) { + $AproxyClassName = str_replace('\\', '', $class->name) . 'AProxy'; + $RproxyClassName = str_replace('\\', '', $class->name) . 'RProxy'; + $AproxyFileName = $proxyDir . $AproxyClassName . '.php'; + $RproxyFileName = $proxyDir . $RproxyClassName . '.php'; + $this->_generateProxyClass($class, $RproxyClassName, $RproxyFileName, self::$_proxyClassTemplate); + $this->_generateProxyClass($class, $AproxyClassName, $AproxyFileName, self::$_assocProxyClassTemplate); + } + } + + /** + * Generates a (reference or association) proxy class. + * + * @param $class + * @param $originalClassName + * @param $proxyClassName + * @param $file The path of the file to write to. + * @return void + */ + private function _generateProxyClass($class, $proxyClassName, $fileName, $file) + { + $methods = $this->_generateMethods($class); + $sleepImpl = $this->_generateSleep($class); + $constructorInv = $class->reflClass->hasMethod('__construct') ? 'parent::__construct();' : ''; + + $placeholders = array( + '', + '', '', + '', '', + '' + ); + $replacements = array( + $this->_proxyNamespace, + $proxyClassName, $class->name, + $methods, $sleepImpl, + $constructorInv + ); + + $file = str_replace($placeholders, $replacements, $file); + + file_put_contents($fileName, $file); + } + + /** + * Generates the methods of a proxy class. + * + * @param ClassMetadata $class + * @return string The code of the generated methods. + */ + private function _generateMethods(ClassMetadata $class) + { + $methods = ''; + + foreach ($class->reflClass->getMethods() as $method) { + if ($method->isConstructor()) { + continue; + } + + if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) { + $methods .= PHP_EOL . ' public function ' . $method->getName() . '('; + $firstParam = true; + $parameterString = $argumentString = ''; + + foreach ($method->getParameters() as $param) { + if ($firstParam) { + $firstParam = false; + } else { + $parameterString .= ', '; + $argumentString .= ', '; + } + + // We need to pick the type hint class too + if (($paramClass = $param->getClass()) !== null) { + $parameterString .= '\\' . $paramClass->getName() . ' '; + } else if ($param->isArray()) { + $parameterString .= 'array '; + } + + if ($param->isPassedByReference()) { + $parameterString .= '&'; + } + + $parameterString .= '$' . $param->getName(); + $argumentString .= '$' . $param->getName(); + + if ($param->isDefaultValueAvailable()) { + $parameterString .= ' = ' . var_export($param->getDefaultValue(), true); + } + } + + $methods .= $parameterString . ') {' . PHP_EOL; + $methods .= ' $this->_load();' . PHP_EOL; + $methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');'; + $methods .= PHP_EOL . ' }' . PHP_EOL; + } + } + + return $methods; + } + + /** + * Generates the code for the __sleep method for a proxy class. + * + * @param $class + * @return string + */ + private function _generateSleep(ClassMetadata $class) + { + $sleepImpl = ''; + + if ($class->reflClass->hasMethod('__sleep')) { + $sleepImpl .= 'return parent::__sleep();'; + } else { + $sleepImpl .= 'return array('; + $first = true; + + foreach ($class->getReflectionProperties() as $name => $prop) { + if ($first) { + $first = false; + } else { + $sleepImpl .= ', '; + } + + $sleepImpl .= "'" . $name . "'"; + } + + $sleepImpl .= ');'; + } + + return $sleepImpl; + } + + /** Reference Proxy class code template */ + private static $_proxyClassTemplate = +' { + /** + * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. + */ + class extends \ implements \Doctrine\ORM\Proxy\Proxy { + private $_entityPersister; + private $_identifier; + private $_loaded = false; + public function __construct($entityPersister, $identifier) { + $this->_entityPersister = $entityPersister; + $this->_identifier = $identifier; + + } + private function _load() { + if ( ! $this->_loaded) { + $this->_entityPersister->load($this->_identifier, $this); + unset($this->_entityPersister); + unset($this->_identifier); + $this->_loaded = true; + } + } + + + + public function __sleep() { + if (!$this->_loaded) { + throw new \RuntimeException("Not fully loaded proxy can not be serialized."); + } + + } + } +}'; + + /** Association Proxy class code template */ + private static $_assocProxyClassTemplate = +' { + /** + * THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE. + */ + class extends \ implements \Doctrine\ORM\Proxy\Proxy { + private $_em; + private $_assoc; + private $_owner; + private $_joinColumnValues; + private $_loaded = false; + public function __construct($em, $assoc, $owner, array $joinColumnValues) { + $this->_em = $em; + $this->_assoc = $assoc; + $this->_owner = $owner; + $this->_joinColumnValues = $joinColumnValues; + + } + private function _load() { + if ( ! $this->_loaded) { + $this->_assoc->load($this->_owner, $this, $this->_em, $this->_joinColumnValues); + unset($this->_em); + unset($this->_owner); + unset($this->_assoc); + unset($this->_joinColumnValues); + $this->_loaded = true; + } + } + + + + public function __sleep() { + if (!$this->_loaded) { + throw new \RuntimeException("Not fully loaded proxy can not be serialized."); + } + + } + } +}'; } diff --git a/lib/Doctrine/ORM/Tools/Cli.php b/lib/Doctrine/ORM/Tools/Cli.php index acc9b9008..17f75f9cd 100644 --- a/lib/Doctrine/ORM/Tools/Cli.php +++ b/lib/Doctrine/ORM/Tools/Cli.php @@ -90,6 +90,7 @@ class Cli 'run-sql' => $ns . '\RunSqlTask', 'run-dql' => $ns . '\RunDqlTask', 'convert-mapping' => $ns . '\ConvertMappingTask', + 'generate-proxies'=> $ns . '\GenerateProxiesTask' )); } diff --git a/lib/Doctrine/ORM/Tools/Cli/Tasks/GenerateProxiesTask.php b/lib/Doctrine/ORM/Tools/Cli/Tasks/GenerateProxiesTask.php new file mode 100644 index 000000000..a19e35ebb --- /dev/null +++ b/lib/Doctrine/ORM/Tools/Cli/Tasks/GenerateProxiesTask.php @@ -0,0 +1,110 @@ + + * @author Jonathan Wage + * @author Roman Borschel + */ +class GenerateProxiesTask extends AbstractTask +{ + /** + * @inheritdoc + */ + public function extendedHelp() + { + $printer = $this->getPrinter(); + + $printer->write('Task: ')->writeln('generate-proxies', 'KEYWORD') + ->write('Synopsis: '); + $this->_writeSynopsis($printer); + + $printer->writeln('Description: Generates proxy classes for entity classes.') + ->writeln('Options:') + ->write('--to-dir', 'OPT_ARG') + ->writeln("\t\tGenerates the classes in the specified directory.") + ->write(PHP_EOL); + } + + /** + * @inheritdoc + */ + public function basicHelp() + { + $this->_writeSynopsis($this->getPrinter()); + } + + private function _writeSynopsis($printer) + { + $printer->write('generate-proxies', 'KEYWORD') + ->writeln(' [--to-dir=]', 'OPT_ARG'); + } + + /** + * @inheritdoc + */ + public function validate() + { + if ( ! parent::validate()) { + return false; + } + + $args = $this->getArguments(); + $printer = $this->getPrinter(); + + if ( ! $this->_requireEntityManager()) { + return false; + } + + $metadataDriver = $this->_em->getConfiguration()->getMetadataDriverImpl(); + if ($metadataDriver instanceof \Doctrine\ORM\Mapping\Driver\AnnotationDriver) { + if ( ! isset($args['class-dir'])) { + $printer->writeln("The supplied configuration uses the annotation metadata driver." + . " The 'class-dir' argument is required for this driver.", 'ERROR'); + return false; + } else { + $metadataDriver->setClassDirectory($args['class-dir']); + } + } + + return true; + } + + /** + * Executes the task. + */ + public function run() + { + $args = $this->getArguments(); + + $cmf = $this->_em->getMetadataFactory(); + $driver = $this->_em->getConfiguration()->getMetadataDriverImpl(); + + $classes = array(); + $preloadedClasses = $driver->preload(true); + foreach ($preloadedClasses as $className) { + $classes[] = $cmf->getMetadataFor($className); + } + + $printer = $this->getPrinter(); + $factory = $this->_em->getProxyFactory(); + + if (empty($classes)) { + $printer->writeln('No classes to process.', 'INFO'); + return; + } + + $factory->generateProxyClasses($classes, isset($args['to-dir']) ? $args['to-dir'] : null); + + $printer->writeln('Proxy classes generated to: ' . + (isset($args['to-dir']) ? $args['to-dir'] : $this->_em->getConfiguration()->getProxyDir()) + ); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 97a53b802..bbcc50cdc 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1331,12 +1331,10 @@ class UnitOfWork implements PropertyChangedListener } else { $assoc2 = $class->associationMappings[$name]; if ($assoc2->isOneToOne() && ! $assoc2->isCascadeMerge) { - //TODO: Only do this when allowPartialObjects == false? $targetClass = $this->_em->getClassMetadata($assoc2->targetEntityName); $prop->setValue($managedCopy, $this->_em->getProxyFactory() ->getReferenceProxy($assoc2->targetEntityName, $targetClass->getIdentifierValues($entity))); } else { - //TODO: Only do this when allowPartialObjects == false? $coll = new PersistentCollection($this->_em, $this->_em->getClassMetadata($assoc2->targetEntityName), new ArrayCollection diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php index 4acbb5f98..36c240bd5 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/MySqlSchemaManagerTest.php @@ -54,7 +54,7 @@ class MySqlSchemaManagerTest extends SchemaManagerFunctionalTestCase $this->assertEquals('id', $columns[0]['name']); $this->assertEquals(true, $columns[0]['primary']); $this->assertEquals('Doctrine\DBAL\Types\IntegerType', get_class($columns[0]['type'])); - $this->assertEquals(4, $columns[0]['length']); + $this->assertEquals(null, $columns[0]['length']); $this->assertEquals(false, $columns[0]['unsigned']); $this->assertEquals(false, $columns[0]['fixed']); $this->assertEquals(true, $columns[0]['notnull']); diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/PostgreSqlSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/PostgreSqlSchemaManagerTest.php index 33426a0d4..0980c56a7 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/PostgreSqlSchemaManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/PostgreSqlSchemaManagerTest.php @@ -52,8 +52,7 @@ class PostgreSqlSchemaManagerTest extends SchemaManagerFunctionalTestCase $this->assertEquals('id', $columns[0]['name']); $this->assertEquals(true, $columns[0]['primary']); $this->assertEquals('Doctrine\DBAL\Types\IntegerType', get_class($columns[0]['type'])); - $this->assertEquals(4, $columns[0]['length']); - $this->assertEquals(false, $columns[0]['unsigned']); + $this->assertEquals(null, $columns[0]['length']); $this->assertEquals(false, $columns[0]['fixed']); $this->assertEquals(true, $columns[0]['notnull']); $this->assertEquals(null, $columns[0]['default']); @@ -62,7 +61,6 @@ class PostgreSqlSchemaManagerTest extends SchemaManagerFunctionalTestCase $this->assertEquals(false, $columns[1]['primary']); $this->assertEquals('Doctrine\DBAL\Types\StringType', get_class($columns[1]['type'])); $this->assertEquals(255, $columns[1]['length']); - $this->assertEquals(false, $columns[1]['unsigned']); $this->assertEquals(false, $columns[1]['fixed']); $this->assertEquals(false, $columns[1]['notnull']); $this->assertEquals(null, $columns[1]['default']); diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php index 2f525beb7..3838f1ca2 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php @@ -58,7 +58,7 @@ class SqliteSchemaManagerTest extends SchemaManagerFunctionalTestCase $this->assertEquals('id', $tableColumns[0]['name']); $this->assertEquals(true, $tableColumns[0]['primary']); $this->assertEquals('Doctrine\DBAL\Types\IntegerType', get_class($tableColumns[0]['type'])); - $this->assertEquals(4, $tableColumns[0]['length']); + $this->assertEquals(null, $tableColumns[0]['length']); $this->assertEquals(false, $tableColumns[0]['unsigned']); $this->assertEquals(false, $tableColumns[0]['fixed']); $this->assertEquals(true, $tableColumns[0]['notnull']); @@ -140,7 +140,7 @@ class SqliteSchemaManagerTest extends SchemaManagerFunctionalTestCase $this->assertEquals('id', $tableColumns[0]['name']); $this->assertEquals(true, $tableColumns[0]['primary']); $this->assertEquals('Doctrine\DBAL\Types\IntegerType', get_class($tableColumns[0]['type'])); - $this->assertEquals(4, $tableColumns[0]['length']); + $this->assertEquals(null, $tableColumns[0]['length']); $this->assertEquals(false, $tableColumns[0]['unsigned']); $this->assertEquals(false, $tableColumns[0]['fixed']); $this->assertEquals(true, $tableColumns[0]['notnull']); diff --git a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php index 45b02860e..dd4a36ca4 100644 --- a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php +++ b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php @@ -20,6 +20,7 @@ */ namespace Doctrine\Tests\Mocks; + use Doctrine\ORM\Proxy\ProxyFactory; /** @@ -75,6 +76,8 @@ class EntityManagerMock extends \Doctrine\ORM\EntityManager { if (is_null($config)) { $config = new \Doctrine\ORM\Configuration(); + $config->setProxyDir(__DIR__ . '/../Proxies'); + $config->setProxyNamespace('Doctrine\Tests\Proxies'); } if (is_null($eventManager)) { $eventManager = new \Doctrine\Common\EventManager(); diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php index 727bbb2ea..48145868d 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php @@ -92,6 +92,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional $this->assertNull($customer->getMentor()); $this->assertTrue($customer->getCart() instanceof ECommerceCart); + $this->assertTrue($customer->getCart() instanceof \Doctrine\ORM\Proxy\Proxy); $this->assertEquals('paypal', $customer->getCart()->getPayment()); } diff --git a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php index 58ddee092..c0ddc0ffc 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php @@ -20,7 +20,11 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase { $this->useModelSet('ecommerce'); parent::setUp(); - $this->_factory = new ProxyFactory($this->_em, new ProxyClassGenerator($this->_em)); + $this->_factory = new ProxyFactory( + $this->_em, + __DIR__ . '/../../Proxies', + 'Doctrine\Tests\Proxies', + true); } public function testLazyLoadsFieldValuesFromDatabase() diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php index 6d3de5901..130a2c8e1 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php @@ -18,6 +18,8 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase { $driverMock = new DriverMock(); $config = new \Doctrine\ORM\Configuration(); + $config->setProxyDir(__DIR__ . '/../../Proxies'); + $config->setProxyNamespace('Doctrine\Tests\Proxies'); $eventManager = new EventManager(); $conn = new ConnectionMock(array(), $driverMock, $config, $eventManager); $mockDriver = new MetadataDriverMock(); diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php index c6e6742a0..cdbefb510 100644 --- a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php @@ -2,9 +2,10 @@ namespace Doctrine\Tests\ORM\Proxy; -use Doctrine\ORM\Proxy\ProxyClassGenerator; +use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\Tests\Mocks\ConnectionMock; use Doctrine\Tests\Mocks\EntityManagerMock; +use Doctrine\Tests\Mocks\UnitOfWorkMock; use Doctrine\Tests\Models\ECommerce\ECommerceCart; use Doctrine\Tests\Models\ECommerce\ECommerceCustomer; use Doctrine\Tests\Models\ECommerce\ECommerceFeature; @@ -20,16 +21,19 @@ require_once __DIR__ . '/../../TestInit.php'; class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase { private $_connectionMock; + private $_uowMock; private $_emMock; - private $_generator; + private $_proxyFactory; protected function setUp() { parent::setUp(); - // SUT $this->_connectionMock = new ConnectionMock(array(), new \Doctrine\Tests\Mocks\DriverMock()); $this->_emMock = EntityManagerMock::create($this->_connectionMock); - $this->_generator = new ProxyClassGenerator($this->_emMock, __DIR__ . '/generated'); + $this->_uowMock = new UnitOfWorkMock($this->_emMock); + $this->_emMock->setUnitOfWork($this->_uowMock); + // SUT + $this->_proxyFactory = new ProxyFactory($this->_emMock, __DIR__ . '/generated', 'Proxies', true); } protected function tearDown() @@ -41,67 +45,29 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase } } - public function testCanGuessADefaultTempFolder() - { - $generator = new ProxyClassGenerator($this->_emMock); - $proxyClass = $generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceShipping'); - $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceShipping')); - } - - public function testCreatesReferenceProxyAsSubclassOfTheOriginalOne() - { - $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); - $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceFeature')); - } - - public function testAllowsReferenceProxyForClassesWithAConstructor() - { - $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceCart'); - $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceCart')); - } - - public function testAllowsIdempotentCreationOfReferenceProxyClass() - { - $originalClassName = 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'; - $proxyClass = $this->_generator->generateReferenceProxyClass($originalClassName); - $theSameProxyClass = $this->_generator->generateReferenceProxyClass($originalClassName); - $this->assertEquals($proxyClass, $theSameProxyClass); - } - - public function testRegeneratesMetadataAfterIdempotentCreation() - { - $originalClassName = 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'; - $metadataFactory = $this->_emMock->getMetadataFactory(); - $proxyClass = $this->_generator->generateReferenceProxyClass($originalClassName); - $metadataFactory->setMetadataFor($proxyClass, null); - $theSameProxyClass = $this->_generator->generateReferenceProxyClass($originalClassName); - $this->assertNotNull($metadataFactory->getMetadataFor($theSameProxyClass)); - } - - public function testReferenceProxyRequiresPersisterInTheConstructor() - { - $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); - $proxy = new $proxyClass($this->_getMockPersister(), null); - } - public function testReferenceProxyDelegatesLoadingToThePersister() { $identifier = array('id' => 42); - $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy'; $persister = $this->_getMockPersister(); - $proxy = new $proxyClass($persister, $identifier); + $this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); + + $proxy = $this->_proxyFactory->getReferenceProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier); + $persister->expects($this->atLeastOnce()) ->method('load') ->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass)); + $proxy->getDescription(); } public function testReferenceProxyExecutesLoadingOnlyOnce() { $identifier = array('id' => 42); - $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy'; $persister = $this->_getMockPersister(); - $proxy = new $proxyClass($persister, $identifier); + $this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); + $proxy = $this->_proxyFactory->getReferenceProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier); $persister->expects($this->atLeastOnce()) ->method('load') ->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass)); @@ -111,8 +77,10 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase public function testReferenceProxyRespectsMethodsParametersTypeHinting() { - $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); - $proxy = new $proxyClass($this->_getMockPersister(), null); + $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy'; + $persister = $this->_getMockPersister(); + $this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); + $proxy = $this->_proxyFactory->getReferenceProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', null); $method = new \ReflectionMethod(get_class($proxy), 'setProduct'); $params = $method->getParameters(); @@ -121,52 +89,28 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $params[0]->getClass()->getName()); } - protected function _getMockPersister() - { - $persister = $this->getMock('Doctrine\ORM\Persisters\StandardEntityPersister', array('load'), array(), '', false, false, false); - return $persister; - } - public function testCreatesAssociationProxyAsSubclassOfTheOriginalOne() { - $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); - $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceFeature')); + $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy'; + $this->assertTrue(is_subclass_of($proxyClass, 'Doctrine\Tests\Models\ECommerce\ECommerceFeature')); } - public function testAllowsAssociationProxyOfClassesWithAConstructor() - { - $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceCart'); - $this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceCart')); - } - - public function testAllowsIdempotentCreationOfAssociationProxyClass() - { - $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); - $theSameProxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); - $this->assertEquals($proxyClass, $theSameProxyClass); - } public function testAllowsConcurrentCreationOfBothProxyTypes() { - $referenceProxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); - $associationProxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $referenceProxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy'; + $associationProxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureAProxy'; $this->assertNotEquals($referenceProxyClass, $associationProxyClass); } - public function testAssociationProxyRequiresEntityManagerAssociationOwnerAndForeignKeysInTheConstructor() - { - $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); - $product = new ECommerceProduct; - $proxy = new $proxyClass($this->_emMock, $this->_getAssociationMock(), $product, array()); - } - public function testAssociationProxyDelegatesLoadingToTheAssociation() { - $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureAProxy'; $product = new ECommerceProduct; $foreignKeys = array('customer_id' => 42); $assoc = $this->_getAssociationMock(); - $proxy = new $proxyClass($this->_emMock, $assoc, $product, $foreignKeys); + $assoc->targetEntityName = 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'; + $proxy = $this->_proxyFactory->getAssociationProxy($product, $assoc, $foreignKeys); $assoc->expects($this->atLeastOnce()) ->method('load') ->with($product, $this->isInstanceOf($proxyClass), $this->isInstanceOf('Doctrine\Tests\Mocks\EntityManagerMock'), $foreignKeys); @@ -175,12 +119,11 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase public function testAssociationProxyExecutesLoadingOnlyOnce() { - $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureAProxy'; $assoc = $this->_getAssociationMock(); - $proxy = new $proxyClass($this->_emMock, $assoc, null, array()); - $assoc->expects($this->once()) - ->method('load'); - + $assoc->targetEntityName = 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'; + $proxy = $this->_proxyFactory->getAssociationProxy(null, $assoc, array()); + $assoc->expects($this->once())->method('load'); $proxy->getDescription(); $proxy->getDescription(); } @@ -190,4 +133,10 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase $assoc = $this->getMock('Doctrine\ORM\Mapping\AssociationMapping', array('load'), array(), '', false, false, false); return $assoc; } + + protected function _getMockPersister() + { + $persister = $this->getMock('Doctrine\ORM\Persisters\StandardEntityPersister', array('load'), array(), '', false, false, false); + return $persister; + } } diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index bd35e5fb7..105d2fee2 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -167,6 +167,8 @@ class OrmFunctionalTestCase extends OrmTestCase $config = new \Doctrine\ORM\Configuration(); $config->setMetadataCacheImpl(self::$_metadataCacheImpl); $config->setQueryCacheImpl(self::$_queryCacheImpl); + $config->setProxyDir(__DIR__ . '/Proxies'); + $config->setProxyNamespace('Doctrine\Tests\Proxies'); $conn = $this->sharedFixture['conn']; return \Doctrine\ORM\EntityManager::create($conn, $config); diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php index 8886773a3..ac60d3711 100644 --- a/tests/Doctrine/Tests/OrmTestCase.php +++ b/tests/Doctrine/Tests/OrmTestCase.php @@ -27,6 +27,8 @@ class OrmTestCase extends DoctrineTestCase $config = new \Doctrine\ORM\Configuration(); $config->setMetadataCacheImpl(self::getSharedMetadataCacheImpl()); $config->setQueryCacheImpl(self::getSharedQueryCacheImpl()); + $config->setProxyDir(__DIR__ . '/Proxies'); + $config->setProxyNamespace('Doctrine\Tests\Proxies'); $eventManager = new \Doctrine\Common\EventManager(); if ($conn === null) { $conn = array( diff --git a/tools/sandbox/cli-config.php b/tools/sandbox/cli-config.php index c7c0c4c9f..3eb2fd262 100644 --- a/tools/sandbox/cli-config.php +++ b/tools/sandbox/cli-config.php @@ -19,8 +19,14 @@ $classLoader = new \Doctrine\Common\IsolatedClassLoader('Entities'); $classLoader->setBasePath(__DIR__); $classLoader->register(); +$classLoader = new \Doctrine\Common\IsolatedClassLoader('Proxies'); +$classLoader->setBasePath(__DIR__); +$classLoader->register(); + $config = new \Doctrine\ORM\Configuration(); $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); +$config->setProxyDir(__DIR__ . '/Proxies'); +$config->setProxyNamespace('Proxies'); $connectionOptions = array( 'driver' => 'pdo_sqlite', diff --git a/tools/sandbox/index.php b/tools/sandbox/index.php index c13ce64b0..2e72e374e 100644 --- a/tools/sandbox/index.php +++ b/tools/sandbox/index.php @@ -1,23 +1,39 @@ registerNamespace('Doctrine', realpath(__DIR__ . '/../../lib')); $classLoader->registerNamespace('Entities', __DIR__); +$classLoader->registerNamespace('Proxies', __DIR__); $classLoader->register(); // Set up caches -$config = new \Doctrine\ORM\Configuration; -$cache = new \Doctrine\Common\Cache\ApcCache; +$config = new Configuration; +$cache = new ApcCache; $config->setMetadataCacheImpl($cache); $config->setQueryCacheImpl($cache); +// Proxy configuration +$config->setProxyDir(__DIR__ . '/Proxies'); +$config->setProxyNamespace('Proxies'); + // Database connection information $connectionOptions = array( 'driver' => 'pdo_sqlite', @@ -25,11 +41,11 @@ $connectionOptions = array( ); // Create EntityManager -$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); +$em = EntityManager::create($connectionOptions, $config); ## PUT YOUR TEST CODE BELOW $user = new User; $address = new Address; -echo 'Hello World!'; \ No newline at end of file +echo 'Hello World!' . PHP_EOL; \ No newline at end of file