Skip to content

Commit

Permalink
BUGFIX: PHP 7.4 property type declarations
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 3a597e3 commit 49ccdc1
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,13 @@ protected function autowireProperties(array &$objectConfigurations)
if (!array_key_exists($propertyName, $properties)) {
/** @var Inject $injectAnnotation */
$injectAnnotation = $this->reflectionService->getPropertyAnnotation($className, $propertyName, Inject::class);
$objectName = $injectAnnotation->name !== null ? $injectAnnotation->name : trim(implode('', $this->reflectionService->getPropertyTagValues($className, $propertyName, 'var')), ' \\');
$objectName = $injectAnnotation->name;
if ($objectName === null) {
$objectName = $this->reflectionService->getPropertyType($className, $propertyName);
}
if ($objectName === null) {
$objectName = trim(implode('', $this->reflectionService->getPropertyTagValues($className, $propertyName, 'var')), ' \\');
}
$configurationProperty = new ConfigurationProperty($propertyName, $objectName, ConfigurationProperty::PROPERTY_TYPES_OBJECT, null, $injectAnnotation->lazy);
$properties[$propertyName] = $configurationProperty;
}
Expand Down
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 @@ -11,6 +11,8 @@
* source code.
*/

use Neos\Flow\ObjectManagement\Exception\CannotBuildObjectException;

/**
* Boilerplate code for dependency injection
*/
Expand All @@ -27,14 +29,18 @@ trait PropertyInjectionTrait
* @param callable $lazyInjectionResolver
* @return void
*/
private function Flow_Proxy_LazyPropertyInjection($propertyObjectName, $propertyClassName, $propertyName, $setterArgumentHash, callable $lazyInjectionResolver)
private function Flow_Proxy_LazyPropertyInjection(string $propertyObjectName, string $propertyClassName, string $propertyName, string $setterArgumentHash, callable $lazyInjectionResolver)
{
$injection_reference = &$this->$propertyName;
$this->$propertyName = \Neos\Flow\Core\Bootstrap::$staticObjectManager->getInstance($propertyObjectName);
if ($this->$propertyName === null) {
$this->$propertyName = \Neos\Flow\Core\Bootstrap::$staticObjectManager->getLazyDependencyByHash($setterArgumentHash, $injection_reference);
if ($this->$propertyName === null) {
$this->$propertyName = \Neos\Flow\Core\Bootstrap::$staticObjectManager->createLazyDependency($setterArgumentHash, $injection_reference, $propertyClassName, $lazyInjectionResolver);
if ($this->$propertyName === null) {
// Fall back to eager injection, if lazy property injection was not possible
$this->$propertyName = \Neos\Flow\Core\Bootstrap::$staticObjectManager->get($propertyObjectName);
}
}
}
}
Expand Down
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,33 @@ public function buildPropertyInjectionCodeByString(Configuration $objectConfigur
return $result;
}

if ($propertyConfiguration->isLazyLoading() && $this->objectConfigurations[$propertyObjectName]->getScope() !== Configuration::SCOPE_PROTOTYPE) {
$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;
}
}
}

# There are several cases when providing lazy loading is not possible, due to the way
# lazy loading is implemented. If any of the check fails, we fall back to eager loading:
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
) {
return $this->buildLazyPropertyInjectionCode($propertyObjectName, $propertyClassName, $propertyName, $preparedSetterArgument);
} else {
return [' $this->' . $propertyName . ' = ' . $preparedSetterArgument . ';'];
Expand Down Expand Up @@ -628,20 +656,21 @@ protected function buildMethodParametersCode(array $argumentConfigurations)
} else {
if (strpos($argumentValue, '.') !== false) {
$settingPath = explode('.', $argumentValue);
$settings = Arrays::getValueByPath($this->configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS), array_shift($settingPath));
$settings = $this->configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS);
$settings = Arrays::getValueByPath($settings, array_shift($settingPath));
$argumentValue = Arrays::getValueByPath($settings, $settingPath);
}
$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
Loading

0 comments on commit 49ccdc1

Please sign in to comment.