Commit cb1c7bce authored by romanb's avatar romanb

[2.0] DBAL cleanups (DDC-46). Proxy class improvements (DDC-19, DDC-39)....

[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.
parent b9f74489
> **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
-
......@@ -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
......@@ -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;
}
......
......@@ -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 <kvesteri@cc.hut.fi>
* @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
* @author Roman Borschel <roman@code-factory.org>
* @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;
......
......@@ -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':
......
......@@ -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
);
......
......@@ -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';
......
......@@ -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
......@@ -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 <roman@code-factory.org>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Roman Borschel <roman@code-factory.org>
*/
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());
}
/**
......
......@@ -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())
{
......
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
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 <roman@code-factory.org>
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
* @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(
'<proxyClassName>', '<className>',
'<methods>', '<sleepImpl>',
'<constructorInvocation>'
);
$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 =
'<?php
/** This class was generated by the Doctrine ORM. DO NOT EDIT THIS FILE. */
namespace Doctrine\Generated\Proxies {
class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy {
private $_entityPersister;
private $_identifier;
private $_loaded = false;
public function __construct($entityPersister, $identifier) {
$this->_entityPersister = $entityPersister;
$this->_identifier = $identifier;
<constructorInvocation>
}
private function _load() {
if ( ! $this->_loaded) {
$this->_entityPersister->load($this->_identifier, $this);
unset($this->_entityPersister);
unset($this->_identifier);
$this->_loaded = true;
}
}
<methods>
public function __sleep() {
if (!$this->_loaded) {
throw new \RuntimeException("Not fully loaded proxy can not be serialized.");
}
<sleepImpl>
}
}
}';
private static $_assocProxyClassTemplate =
'<?php
/** This class was generated by the Doctrine ORM. DO NOT EDIT THIS FILE. */
namespace Doctrine\Generated\Proxies {
class <proxyClassName> extends \<className> 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;
<constructorInvocation>
}
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;
}
}
<methods>
public function __sleep() {
if (!$this->_loaded) {
throw new \RuntimeException("Not fully loaded proxy can not be serialized.");
}
<sleepImpl>
}
}
}';
}
......@@ -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 <roman@code-factory.org>
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
......@@ -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 <tt>DynamicProxyGenerator</tt> class that is
* connected to the given <tt>EntityManager</tt> and stores proxy class files in
* the given cache directory.
* Initializes a new instance of the <tt>ProxyFactory</tt> class that is
* connected to the given <tt>EntityManager</tt>.
*
* @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(
'<namespace>',
'<proxyClassName>', '<className>',
'<methods>', '<sleepImpl>',
'<constructorInvocation>'
);
$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 =
'<?php
namespace <namespace> {
/**
* THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
*/
class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy {
private $_entityPersister;
private $_identifier;
private $_loaded = false;
public function __construct($entityPersister, $identifier) {
$this->_entityPersister = $entityPersister;
$this->_identifier = $identifier;
<constructorInvocation>
}
private function _load() {
if ( ! $this->_loaded) {
$this->_entityPersister->load($this->_identifier, $this);
unset($this->_entityPersister);
unset($this->_identifier);
$this->_loaded = true;
}
}
<methods>
public function __sleep() {
if (!$this->_loaded) {
throw new \RuntimeException("Not fully loaded proxy can not be serialized.");
}
<sleepImpl>
}
}
}';
/** Association Proxy class code template */
private static $_assocProxyClassTemplate =
'<?php
namespace <namespace> {
/**
* THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
*/
class <proxyClassName> extends \<className> 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;
<constructorInvocation>
}
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;
}
}
<methods>
public function __sleep() {
if (!$this->_loaded) {
throw new \RuntimeException("Not fully loaded proxy can not be serialized.");
}
<sleepImpl>
}
}
}';
}
......@@ -90,6 +90,7 @@ class Cli
'run-sql' => $ns . '\RunSqlTask',
'run-dql' => $ns . '\RunDqlTask',
'convert-mapping' => $ns . '\ConvertMappingTask',
'generate-proxies'=> $ns . '\GenerateProxiesTask'
));
}
......
<?php
namespace Doctrine\ORM\Tools\Cli\Tasks;
/**
* Task to (re)generate the proxy classes used by doctrine.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision: 3938 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
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=<PATH>]', '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
......@@ -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
......
......@@ -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']);
......
......@@ -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']);
......
......@@ -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']);
......
......@@ -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();
......
......@@ -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());
}
......
......@@ -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()
......
......@@ -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();
......
......@@ -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'));
}
public function testAllowsAssociationProxyOfClassesWithAConstructor()
{
$proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceCart');
$this->assertTrue(is_subclass_of($proxyClass, '\Doctrine\Tests\Models\ECommerce\ECommerceCart'));
$proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureRProxy';
$this->assertTrue(is_subclass_of($proxyClass, 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'));
}
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;
}
}
......@@ -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);
......
......@@ -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(
......
......@@ -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',
......
<?php
/**
* Welcome to Doctrine 2.
*
* This is the index file of the sandbox. The first section of this file
* demonstrates the bootstrapping and configuration procedure of Doctrine 2.
* Below that section you can place your test code and experiment.
*/
namespace Sandbox;
use Entities\User, Entities\Address;
use Doctrine\ORM\Configuration,
Doctrine\ORM\EntityManager,
Doctrine\Common\Cache\ApcCache,
Entities\User, Entities\Address;
require '../../lib/Doctrine/Common/GlobalClassLoader.php';
// Set up class loading, we could alternatively use 2 IsolatedClassLoaders
// Set up class loading, we could alternatively use several IsolatedClassLoaders.
// You could also use different autoloaders, provided by your favorite framework.
$classLoader = new \Doctrine\Common\GlobalClassLoader();
$classLoader->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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment