Skip to content

Commit

Permalink
BUGFIX: PHP 7.4 property type declarations (WIP)
Browse files Browse the repository at this point in the history
This change adjusts the proxy building and dependency injection process
to properly handle PHP 7.4 class property type declarations.
neos#2114
  • Loading branch information
robertlemke committed Feb 22, 2022
1 parent b567e7c commit de0c42b
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,60 +19,23 @@
* @Flow\Proxy(false)
* @api
*/
class DependencyProxy
interface DependencyProxy
{
/**
* @var string
*/
protected $className;

/**
* @var \Closure
*/
protected $builder;

/**
* @var array
*/
protected $propertyVariables = [];

/**
* Constructs this proxy
*
* @param string $className Implementation class name of the dependency to proxy
* @param \Closure $builder The closure which eventually builds the dependency
*/
public function __construct($className, \Closure $builder)
{
$this->className = $className;
$this->builder = $builder;
}

/**
* Activate the dependency and set it in the object.
*
* @return object The real dependency object
* @api
*/
public function _activateDependency()
{
$realDependency = $this->builder->__invoke();
foreach ($this->propertyVariables as &$propertyVariable) {
$propertyVariable = $realDependency;
}
return $realDependency;
}
public function _activateDependency();

/**
* Returns the class name of the proxied dependency
*
* @return string Fully qualified class name of the proxied object
* @api
*/
public function _getClassName()
{
return $this->className;
}
public function _getClassName();

/**
* Adds another variable by reference where the actual dependency object should
Expand All @@ -81,21 +44,5 @@ public function _getClassName()
* @param mixed &$propertyVariable The variable to replace
* @return void
*/
public function _addPropertyVariable(&$propertyVariable)
{
$this->propertyVariables[] = &$propertyVariable;
}

/**
* Proxy magic call method which triggers the injection of the real dependency
* and returns the result of a call to the original method in the dependency
*
* @param string $methodName Name of the method to be called
* @param array $arguments An array of arguments to be passed to the method
* @return mixed
*/
public function __call($methodName, array $arguments)
{
return $this->_activateDependency()->$methodName(...$arguments);
}
public function _addPropertyVariable(&$propertyVariable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php
namespace Neos\Flow\ObjectManagement\DependencyInjection;

/*
* This file is part of the Neos.Flow package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

trait DependencyProxyTrait
{
/**
* @var string
*/
protected $_dependencyClassName;

/**
* @var \Closure
*/
protected $_dependencyBuilder;

/**
* @var array
*/
protected $_dependencyPropertyVariables = [];

/**
* Override any possibly existing constructor
*/
public function __construct()
{
}

/**
* Constructs this proxy
*
* @param string $className Implementation class name of the dependency to proxy
* @param \Closure $builder The closure which eventually builds the dependency
* @return static
*/
public static function _createDependencyProxy(string $className, \Closure $builder)
{
$instance = new static();
$instance->_dependencyClassName = $className;
$instance->_dependencyBuilder = $builder;
return $instance;
}

/**
* Activate the dependency and set it in the object.
*
* @return object The real dependency object
* @api
*/
public function _activateDependency()
{
$realDependency = $this->_dependencyBuilder->__invoke();
foreach ($this->_dependencyPropertyVariables as &$propertyVariable) {
$propertyVariable = $realDependency;
}
return $realDependency;
}

/**
* Returns the class name of the proxied dependency
*
* @return string Fully qualified class name of the proxied object
* @api
*/
public function _getClassName(): string
{
return $this->_dependencyClassName;
}

/**
* Adds another variable by reference where the actual dependency object should
* be injected into once this proxy is activated.
*
* @param mixed &$propertyVariable The variable to replace
* @return void
*/
public function _addPropertyVariable(&$propertyVariable): void
{
$this->_dependencyPropertyVariables[] = &$propertyVariable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Neos\Flow\ObjectManagement\Proxy\Compiler;
use Neos\Flow\ObjectManagement\Proxy\ObjectSerializationTrait;
use Neos\Flow\ObjectManagement\Proxy\ProxyClass;
use Neos\Flow\Reflection\ClassReflection;
use Neos\Flow\Reflection\MethodReflection;
use Neos\Flow\Reflection\ReflectionService;
use Neos\Utility\Arrays;
Expand Down Expand Up @@ -299,15 +300,15 @@ protected function buildConstructorInjectionCode(Configuration $objectConfigurat
}
$assignments[$argumentPosition] = $assignmentPrologue . '\Neos\Flow\Core\Bootstrap::$staticObjectManager->get(\'' . $argumentValue . '\')';
}
break;
break;

case ConfigurationArgument::ARGUMENT_TYPES_STRAIGHTVALUE:
$assignments[$argumentPosition] = $assignmentPrologue . var_export($argumentValue, true);
break;
break;

case ConfigurationArgument::ARGUMENT_TYPES_SETTING:
$assignments[$argumentPosition] = $assignmentPrologue . '\Neos\Flow\Core\Bootstrap::$staticObjectManager->get(\Neos\Flow\Configuration\ConfigurationManager::class)->getConfiguration(\Neos\Flow\Configuration\ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, \'' . $argumentValue . '\')';
break;
break;
}
}
}
Expand Down Expand Up @@ -360,7 +361,7 @@ protected function buildPropertyInjectionCode(Configuration $objectConfiguration
$commands = array_merge($commands, $this->buildPropertyInjectionCodeByString($objectConfiguration, $propertyConfiguration, $propertyName, $propertyValue));
}

break;
break;
case ConfigurationProperty::PROPERTY_TYPES_STRAIGHTVALUE:
if (is_string($propertyValue)) {
$preparedSetterArgument = '\'' . str_replace('\'', '\\\'', $propertyValue) . '\'';
Expand All @@ -372,14 +373,14 @@ protected function buildPropertyInjectionCode(Configuration $objectConfiguration
$preparedSetterArgument = $propertyValue;
}
$commands[] = 'if (\Neos\Utility\ObjectAccess::setProperty($this, \'' . $propertyName . '\', ' . $preparedSetterArgument . ') === false) { $this->' . $propertyName . ' = ' . $preparedSetterArgument . ';}';
break;
break;
case ConfigurationProperty::PROPERTY_TYPES_CONFIGURATION:
$configurationType = $propertyValue['type'];
if (!in_array($configurationType, $this->configurationManager->getAvailableConfigurationTypes())) {
throw new ObjectException\UnknownObjectException('The configuration injection specified for property "' . $propertyName . '" in the object configuration of object "' . $objectConfiguration->getObjectName() . '" refers to the unknown configuration type "' . $configurationType . '".', 1420736211);
}
$commands = array_merge($commands, $this->buildPropertyInjectionCodeByConfigurationTypeAndPath($objectConfiguration, $propertyName, $configurationType, $propertyValue['path']));
break;
break;
}
$injectedProperties[] = $propertyName;
}
Expand All @@ -406,7 +407,6 @@ protected function buildPropertyInjectionCode(Configuration $objectConfiguration
protected function buildPropertyInjectionCodeByConfiguration(Configuration $objectConfiguration, $propertyName, Configuration $propertyConfiguration)
{
$className = $objectConfiguration->getClassName();
$propertyObjectName = $propertyConfiguration->getObjectName();
$propertyClassName = $propertyConfiguration->getClassName();
if ($propertyClassName === null) {
$preparedSetterArgument = $this->buildCustomFactoryCall($propertyConfiguration->getFactoryObjectName(), $propertyConfiguration->getFactoryMethodName(), $propertyConfiguration->getFactoryArguments());
Expand All @@ -427,7 +427,9 @@ protected function buildPropertyInjectionCodeByConfiguration(Configuration $obje
return $result;
}

return $this->buildLazyPropertyInjectionCode($propertyObjectName, $propertyClassName, $propertyName, $preparedSetterArgument);
// It's hard to predict what we are going to inject due to what can be configured via Objects.yaml,
// so we don't allow lazy injection in this case:
return [' $this->' . $propertyName . ' = ' . $preparedSetterArgument . ';'];
}

/**
Expand Down Expand Up @@ -466,7 +468,34 @@ public function buildPropertyInjectionCodeByString(Configuration $objectConfigur
return $result;
}

if ($propertyConfiguration->isLazyLoading() && $this->objectConfigurations[$propertyObjectName]->getScope() !== Configuration::SCOPE_PROTOTYPE) {
// FIXME – just for testing
$propertyClassHasInterfaceWithConstructor = false;
$propertyClassHasFinalMethod = false;
if ($propertyClassName) {
foreach ((new ClassReflection($propertyClassName))->getInterfaceNames() as $interfaceName) {
if (method_exists($interfaceName, '__construct')) {
$propertyClassHasInterfaceWithConstructor = true;
break;
}
}
foreach (get_class_methods($propertyClassName) as $methodName) {
if ($this->reflectionService->isMethodFinal($propertyClassName, $methodName)) {
$propertyClassHasFinalMethod = true;
}
}
}

if (
$propertyConfiguration->isLazyLoading() &&
$this->objectConfigurations[$propertyObjectName]->getScope() !== Configuration::SCOPE_PROTOTYPE &&
!interface_exists($this->objectConfigurations[$propertyObjectName]->getClassName()) &&
!$this->objectConfigurations[$propertyObjectName]->isCreatedByFactory() &&
!$propertyClassHasInterfaceWithConstructor &&
!$propertyClassHasFinalMethod &&
$this->compiler->getProxyClass($propertyClassName) !== false

// FIXME MORE
) {
return $this->buildLazyPropertyInjectionCode($propertyObjectName, $propertyClassName, $propertyName, $preparedSetterArgument);
} else {
return [' $this->' . $propertyName . ' = ' . $preparedSetterArgument . ';'];
Expand Down Expand Up @@ -633,15 +662,15 @@ protected function buildMethodParametersCode(array $argumentConfigurations)
}
$preparedArguments[] = '\Neos\Flow\Core\Bootstrap::$staticObjectManager->get(\'' . $argumentValue . '\')';
}
break;
break;

case ConfigurationArgument::ARGUMENT_TYPES_STRAIGHTVALUE:
$preparedArguments[] = var_export($argumentValue, true);
break;
break;

case ConfigurationArgument::ARGUMENT_TYPES_SETTING:
$preparedArguments[] = '\Neos\Flow\Core\Bootstrap::$staticObjectManager->get(\Neos\Flow\Configuration\ConfigurationManager::class)->getConfiguration(\Neos\Flow\Configuration\ConfigurationManager::CONFIGURATION_TYPE_SETTINGS, \'' . $argumentValue . '\')';
break;
break;
}
}
}
Expand Down
13 changes: 9 additions & 4 deletions Neos.Flow/Classes/ObjectManagement/ObjectManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -406,16 +406,21 @@ public function getLazyDependencyByHash($hash, &$propertyReferenceVariable)
* @param string &$propertyReferenceVariable A first variable where the dependency needs to be injected into
* @param string $className Name of the class of the dependency which eventually will be instantiated
* @param \Closure $builder An anonymous function which creates the instance to be injected
* @return DependencyProxy
* @return DependencyProxy | null
*/
public function createLazyDependency($hash, &$propertyReferenceVariable, $className, \Closure $builder): DependencyProxy
public function createLazyDependency($hash, &$propertyReferenceVariable, $className, \Closure $builder): ?DependencyProxy
{
$this->dependencyProxies[$hash] = new DependencyProxy($className, $builder);
// Trigger auto-loading of the original class, because any generated lazy proxy will be
// contained in the same file and the auto-loader does not know how to find a lazy proxy:
class_exists($className) || interface_exists($className);

$lazyProxyClassName = $className . '_LazyProxy';
$this->dependencyProxies[$hash] = call_user_func_array([$lazyProxyClassName, '_createDependencyProxy'], [$className, $builder]);
$this->dependencyProxies[$hash]->_addPropertyVariable($propertyReferenceVariable);

return $this->dependencyProxies[$hash];
}


/**
* Unsets the instance of the given object
*
Expand Down
Loading

0 comments on commit de0c42b

Please sign in to comment.