View source
<?php
namespace Doctrine\Common\Proxy;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
use Doctrine\Common\Proxy\Exception\UnexpectedValueException;
use Doctrine\Common\Util\ClassUtils;
class ProxyGenerator {
const PATTERN_MATCH_ID_METHOD = '((public\\s)?(function\\s{1,}%s\\s?\\(\\)\\s{1,})\\s{0,}{\\s{0,}return\\s{0,}\\$this->%s;\\s{0,}})i';
private $proxyNamespace;
private $proxyDirectory;
protected $placeholders = array(
'baseProxyInterface' => 'Doctrine\\Common\\Proxy\\Proxy',
'additionalProperties' => '',
);
protected $proxyClassTemplate = '<?php
namespace <namespace>;
/**
* DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR
*/
class <proxyShortClassName> extends \\<className> implements \\<baseProxyInterface>
{
/**
* @var \\Closure the callback responsible for loading properties in the proxy object. This callback is called with
* three parameters, being respectively the proxy object to be initialized, the method that triggered the
* initialization process and an array of ordered parameters that were passed to that method.
*
* @see \\Doctrine\\Common\\Persistence\\Proxy::__setInitializer
*/
public $__initializer__;
/**
* @var \\Closure the callback responsible of loading properties that need to be copied in the cloned object
*
* @see \\Doctrine\\Common\\Persistence\\Proxy::__setCloner
*/
public $__cloner__;
/**
* @var boolean flag indicating if this object was already initialized
*
* @see \\Doctrine\\Common\\Persistence\\Proxy::__isInitialized
*/
public $__isInitialized__ = false;
/**
* @var array properties to be lazy loaded, with keys being the property
* names and values being their default values
*
* @see \\Doctrine\\Common\\Persistence\\Proxy::__getLazyProperties
*/
public static $lazyPropertiesDefaults = array(<lazyPropertiesDefaults>);
<additionalProperties>
<constructorImpl>
<magicGet>
<magicSet>
<magicIsset>
<sleepImpl>
<wakeupImpl>
<cloneImpl>
/**
* Forces initialization of the proxy
*/
public function __load()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', array());
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
public function __isInitialized()
{
return $this->__isInitialized__;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
public function __setInitialized($initialized)
{
$this->__isInitialized__ = $initialized;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
public function __setInitializer(\\Closure $initializer = null)
{
$this->__initializer__ = $initializer;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
public function __getInitializer()
{
return $this->__initializer__;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
*/
public function __setCloner(\\Closure $cloner = null)
{
$this->__cloner__ = $cloner;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific cloning logic
*/
public function __getCloner()
{
return $this->__cloner__;
}
/**
* {@inheritDoc}
* @internal generated method: use only when explicitly handling proxy specific loading logic
* @static
*/
public function __getLazyProperties()
{
return self::$lazyPropertiesDefaults;
}
<methods>
}
';
public function __construct($proxyDirectory, $proxyNamespace) {
if (!$proxyDirectory) {
throw InvalidArgumentException::proxyDirectoryRequired();
}
if (!$proxyNamespace) {
throw InvalidArgumentException::proxyNamespaceRequired();
}
$this->proxyDirectory = $proxyDirectory;
$this->proxyNamespace = $proxyNamespace;
}
public function setPlaceholder($name, $placeholder) {
if (!is_string($placeholder) && !is_callable($placeholder)) {
throw InvalidArgumentException::invalidPlaceholder($name);
}
$this->placeholders[$name] = $placeholder;
}
public function setProxyClassTemplate($proxyClassTemplate) {
$this->proxyClassTemplate = (string) $proxyClassTemplate;
}
public function generateProxyClass(ClassMetadata $class, $fileName = false) {
preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches);
$placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]);
$placeholders = array();
foreach ($placeholderMatches as $placeholder => $name) {
$placeholders[$placeholder] = isset($this->placeholders[$name]) ? $this->placeholders[$name] : array(
$this,
'generate' . $name,
);
}
foreach ($placeholders as &$placeholder) {
if (is_callable($placeholder)) {
$placeholder = call_user_func($placeholder, $class);
}
}
$proxyCode = strtr($this->proxyClassTemplate, $placeholders);
if (!$fileName) {
$proxyClassName = $this
->generateNamespace($class) . '\\' . $this
->generateProxyShortClassName($class);
if (!class_exists($proxyClassName)) {
eval(substr($proxyCode, 5));
}
return;
}
$parentDirectory = dirname($fileName);
if (!is_dir($parentDirectory) && false === @mkdir($parentDirectory, 0775, true)) {
throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
}
if (!is_writable($parentDirectory)) {
throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
}
$tmpFileName = $fileName . '.' . uniqid('', true);
file_put_contents($tmpFileName, $proxyCode);
rename($tmpFileName, $fileName);
}
private function generateProxyShortClassName(ClassMetadata $class) {
$proxyClassName = ClassUtils::generateProxyClassName($class
->getName(), $this->proxyNamespace);
$parts = explode('\\', strrev($proxyClassName), 2);
return strrev($parts[0]);
}
private function generateNamespace(ClassMetadata $class) {
$proxyClassName = ClassUtils::generateProxyClassName($class
->getName(), $this->proxyNamespace);
$parts = explode('\\', strrev($proxyClassName), 2);
return strrev($parts[1]);
}
private function generateClassName(ClassMetadata $class) {
return ltrim($class
->getName(), '\\');
}
private function generateLazyPropertiesDefaults(ClassMetadata $class) {
$lazyPublicProperties = $this
->getLazyLoadedPublicProperties($class);
$values = array();
foreach ($lazyPublicProperties as $key => $value) {
$values[] = var_export($key, true) . ' => ' . var_export($value, true);
}
return implode(', ', $values);
}
private function generateConstructorImpl(ClassMetadata $class) {
$constructorImpl = <<<'EOT'
/**
* @param \Closure $initializer
* @param \Closure $cloner
*/
public function __construct($initializer = null, $cloner = null)
{
EOT;
$toUnset = array();
foreach ($this
->getLazyLoadedPublicProperties($class) as $lazyPublicProperty => $unused) {
$toUnset[] = '$this->' . $lazyPublicProperty;
}
$constructorImpl .= (empty($toUnset) ? '' : ' unset(' . implode(', ', $toUnset) . ");\n") . <<<'EOT'
$this->__initializer__ = $initializer;
$this->__cloner__ = $cloner;
}
EOT;
return $constructorImpl;
}
private function generateMagicGet(ClassMetadata $class) {
$lazyPublicProperties = array_keys($this
->getLazyLoadedPublicProperties($class));
$reflectionClass = $class
->getReflectionClass();
$hasParentGet = false;
$returnReference = '';
$inheritDoc = '';
if ($reflectionClass
->hasMethod('__get')) {
$hasParentGet = true;
$inheritDoc = '{@inheritDoc}';
if ($reflectionClass
->getMethod('__get')
->returnsReference()) {
$returnReference = '& ';
}
}
if (empty($lazyPublicProperties) && !$hasParentGet) {
return '';
}
$magicGet = <<<EOT
/**
* {<span class="php-variable">$inheritDoc</span>}
* @param string \$name
*/
public function {<span class="php-variable">$returnReference</span>}__get(\$name)
{
EOT;
if (!empty($lazyPublicProperties)) {
$magicGet .= <<<'EOT'
if (array_key_exists($name, $this->__getLazyProperties())) {
$this->__initializer__ && $this->__initializer__->__invoke($this, '__get', array($name));
return $this->$name;
}
EOT;
}
if ($hasParentGet) {
$magicGet .= <<<'EOT'
$this->__initializer__ && $this->__initializer__->__invoke($this, '__get', array($name));
return parent::__get($name);
EOT;
}
else {
$magicGet .= <<<'EOT'
trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE);
EOT;
}
$magicGet .= " }";
return $magicGet;
}
private function generateMagicSet(ClassMetadata $class) {
$lazyPublicProperties = $this
->getLazyLoadedPublicProperties($class);
$hasParentSet = $class
->getReflectionClass()
->hasMethod('__set');
if (empty($lazyPublicProperties) && !$hasParentSet) {
return '';
}
$inheritDoc = $hasParentSet ? '{@inheritDoc}' : '';
$magicSet = <<<EOT
/**
* {<span class="php-variable">$inheritDoc</span>}
* @param string \$name
* @param mixed \$value
*/
public function __set(\$name, \$value)
{
EOT;
if (!empty($lazyPublicProperties)) {
$magicSet .= <<<'EOT'
if (array_key_exists($name, $this->__getLazyProperties())) {
$this->__initializer__ && $this->__initializer__->__invoke($this, '__set', array($name, $value));
$this->$name = $value;
return;
}
EOT;
}
if ($hasParentSet) {
$magicSet .= <<<'EOT'
$this->__initializer__ && $this->__initializer__->__invoke($this, '__set', array($name, $value));
return parent::__set($name, $value);
EOT;
}
else {
$magicSet .= " \$this->\$name = \$value;";
}
$magicSet .= "\n }";
return $magicSet;
}
private function generateMagicIsset(ClassMetadata $class) {
$lazyPublicProperties = array_keys($this
->getLazyLoadedPublicProperties($class));
$hasParentIsset = $class
->getReflectionClass()
->hasMethod('__isset');
if (empty($lazyPublicProperties) && !$hasParentIsset) {
return '';
}
$inheritDoc = $hasParentIsset ? '{@inheritDoc}' : '';
$magicIsset = <<<EOT
/**
* {<span class="php-variable">$inheritDoc</span>}
* @param string \$name
* @return boolean
*/
public function __isset(\$name)
{
EOT;
if (!empty($lazyPublicProperties)) {
$magicIsset .= <<<'EOT'
if (array_key_exists($name, $this->__getLazyProperties())) {
$this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', array($name));
return isset($this->$name);
}
EOT;
}
if ($hasParentIsset) {
$magicIsset .= <<<'EOT'
$this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', array($name));
return parent::__isset($name);
EOT;
}
else {
$magicIsset .= " return false;";
}
return $magicIsset . "\n }";
}
private function generateSleepImpl(ClassMetadata $class) {
$hasParentSleep = $class
->getReflectionClass()
->hasMethod('__sleep');
$inheritDoc = $hasParentSleep ? '{@inheritDoc}' : '';
$sleepImpl = <<<EOT
/**
* {<span class="php-variable">$inheritDoc</span>}
* @return array
*/
public function __sleep()
{
EOT;
if ($hasParentSleep) {
return $sleepImpl . <<<'EOT'
$properties = array_merge(array('__isInitialized__'), parent::__sleep());
if ($this->__isInitialized__) {
$properties = array_diff($properties, array_keys($this->__getLazyProperties()));
}
return $properties;
}
EOT;
}
$allProperties = array(
'__isInitialized__',
);
foreach ($class
->getReflectionClass()
->getProperties() as $prop) {
if ($prop
->isStatic()) {
continue;
}
$allProperties[] = $prop
->isPrivate() ? "\0" . $prop
->getDeclaringClass()
->getName() . "\0" . $prop
->getName() : $prop
->getName();
}
$lazyPublicProperties = array_keys($this
->getLazyLoadedPublicProperties($class));
$protectedProperties = array_diff($allProperties, $lazyPublicProperties);
foreach ($allProperties as &$property) {
$property = var_export($property, true);
}
foreach ($protectedProperties as &$property) {
$property = var_export($property, true);
}
$allProperties = implode(', ', $allProperties);
$protectedProperties = implode(', ', $protectedProperties);
return $sleepImpl . <<<EOT
if (\$this->__isInitialized__) {
return array({<span class="php-variable">$allProperties</span>});
}
return array({<span class="php-variable">$protectedProperties</span>});
}
EOT;
}
private function generateWakeupImpl(ClassMetadata $class) {
$unsetPublicProperties = array();
$hasWakeup = $class
->getReflectionClass()
->hasMethod('__wakeup');
foreach (array_keys($this
->getLazyLoadedPublicProperties($class)) as $lazyPublicProperty) {
$unsetPublicProperties[] = '$this->' . $lazyPublicProperty;
}
$shortName = $this
->generateProxyShortClassName($class);
$inheritDoc = $hasWakeup ? '{@inheritDoc}' : '';
$wakeupImpl = <<<EOT
/**
* {<span class="php-variable">$inheritDoc</span>}
*/
public function __wakeup()
{
if ( ! \$this->__isInitialized__) {
\$this->__initializer__ = function ({<span class="php-variable">$shortName</span>} \$proxy) {
\$proxy->__setInitializer(null);
\$proxy->__setCloner(null);
\$existingProperties = get_object_vars(\$proxy);
foreach (\$proxy->__getLazyProperties() as \$property => \$defaultValue) {
if ( ! array_key_exists(\$property, \$existingProperties)) {
\$proxy->\$property = \$defaultValue;
}
}
};
EOT;
if (!empty($unsetPublicProperties)) {
$wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ");";
}
$wakeupImpl .= "\n }";
if ($hasWakeup) {
$wakeupImpl .= "\n parent::__wakeup();";
}
$wakeupImpl .= "\n }";
return $wakeupImpl;
}
private function generateCloneImpl(ClassMetadata $class) {
$hasParentClone = $class
->getReflectionClass()
->hasMethod('__clone');
$inheritDoc = $hasParentClone ? '{@inheritDoc}' : '';
$callParentClone = $hasParentClone ? "\n parent::__clone();\n" : '';
return <<<EOT
/**
* {<span class="php-variable">$inheritDoc</span>}
*/
public function __clone()
{
\$this->__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', array());
{<span class="php-variable">$callParentClone</span>} }
EOT;
}
private function generateMethods(ClassMetadata $class) {
$methods = '';
$methodNames = array();
$reflectionMethods = $class
->getReflectionClass()
->getMethods(\ReflectionMethod::IS_PUBLIC);
$skippedMethods = array(
'__sleep' => true,
'__clone' => true,
'__wakeup' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
);
foreach ($reflectionMethods as $method) {
$name = $method
->getName();
if ($method
->isConstructor() || isset($skippedMethods[strtolower($name)]) || isset($methodNames[$name]) || $method
->isFinal() || $method
->isStatic() || !$method
->isPublic()) {
continue;
}
$methodNames[$name] = true;
$methods .= "\n /**\n" . " * {@inheritDoc}\n" . " */\n" . ' public function ';
if ($method
->returnsReference()) {
$methods .= '&';
}
$methods .= $name . '(' . $this
->buildParametersString($class, $method, $method
->getParameters()) . ')';
$methods .= "\n" . ' {' . "\n";
if ($this
->isShortIdentifierGetter($method, $class)) {
$identifier = lcfirst(substr($name, 3));
$fieldType = $class
->getTypeOfField($identifier);
$cast = in_array($fieldType, array(
'integer',
'smallint',
)) ? '(int) ' : '';
$methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
$methods .= ' return ' . $cast . ' parent::' . $method
->getName() . "();\n";
$methods .= ' }' . "\n\n";
}
$invokeParamsString = implode(', ', $this
->getParameterNamesForInvoke($method
->getParameters()));
$callParamsString = implode(', ', $this
->getParameterNamesForParentCall($method
->getParameters()));
$methods .= "\n \$this->__initializer__ " . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true) . ", array(" . $invokeParamsString . "));" . "\n\n return parent::" . $name . '(' . $callParamsString . ');' . "\n" . ' }' . "\n";
}
return $methods;
}
public function getProxyFileName($className, $baseDirectory = null) {
$baseDirectory = $baseDirectory ?: $this->proxyDirectory;
return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER . str_replace('\\', '', $className) . '.php';
}
private function isShortIdentifierGetter($method, ClassMetadata $class) {
$identifier = lcfirst(substr($method
->getName(), 3));
$startLine = $method
->getStartLine();
$endLine = $method
->getEndLine();
$cheapCheck = $method
->getNumberOfParameters() == 0 && substr($method
->getName(), 0, 3) == 'get' && in_array($identifier, $class
->getIdentifier(), true) && $class
->hasField($identifier) && $endLine - $startLine <= 4;
if ($cheapCheck) {
$code = file($method
->getDeclaringClass()
->getFileName());
$code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1)));
$pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method
->getName(), $identifier);
if (preg_match($pattern, $code)) {
return true;
}
}
return false;
}
private function getLazyLoadedPublicProperties(ClassMetadata $class) {
$defaultProperties = $class
->getReflectionClass()
->getDefaultProperties();
$properties = array();
foreach ($class
->getReflectionClass()
->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
$name = $property
->getName();
if (($class
->hasField($name) || $class
->hasAssociation($name)) && !$class
->isIdentifier($name)) {
$properties[$name] = $defaultProperties[$name];
}
}
return $properties;
}
private function buildParametersString(ClassMetadata $class, \ReflectionMethod $method, array $parameters) {
$parameterDefinitions = array();
foreach ($parameters as $param) {
$parameterDefinition = '';
if ($parameterType = $this
->getParameterType($class, $method, $param)) {
$parameterDefinition .= $parameterType . ' ';
}
if ($param
->isPassedByReference()) {
$parameterDefinition .= '&';
}
if (method_exists($param, 'isVariadic')) {
if ($param
->isVariadic()) {
$parameterDefinition .= '...';
}
}
$parameters[] = '$' . $param
->getName();
$parameterDefinition .= '$' . $param
->getName();
if ($param
->isDefaultValueAvailable()) {
$parameterDefinition .= ' = ' . var_export($param
->getDefaultValue(), true);
}
$parameterDefinitions[] = $parameterDefinition;
}
return implode(', ', $parameterDefinitions);
}
private function getParameterType(ClassMetadata $class, \ReflectionMethod $method, \ReflectionParameter $parameter) {
if ($parameter
->isArray()) {
return 'array';
}
if (method_exists($parameter, 'isCallable') && $parameter
->isCallable()) {
return 'callable';
}
try {
$parameterClass = $parameter
->getClass();
if ($parameterClass) {
return '\\' . $parameterClass
->getName();
}
} catch (\ReflectionException $previous) {
throw UnexpectedValueException::invalidParameterTypeHint($class
->getName(), $method
->getName(), $parameter
->getName(), $previous);
}
return null;
}
private function getParameterNamesForInvoke(array $parameters) {
return array_map(function (\ReflectionParameter $parameter) {
return '$' . $parameter
->getName();
}, $parameters);
}
private function getParameterNamesForParentCall(array $parameters) {
return array_map(function (\ReflectionParameter $parameter) {
$name = '';
if (method_exists($parameter, 'isVariadic')) {
if ($parameter
->isVariadic()) {
$name .= '...';
}
}
$name .= '$' . $parameter
->getName();
return $name;
}, $parameters);
}
}