[+FEATURE] make DI work without inject-methods
authorFelix Oertel <f@oer.tel>
Fri, 10 Feb 2012 21:56:24 +0000 (22:56 +0100)
committerStefan Neufeind <typo3.neufeind@speedpartner.de>
Fri, 10 Feb 2012 22:03:36 +0000 (23:03 +0100)
As in FLOW3 it would be great to use dependency injection without
the inject methods. PHP 5.3 allows to set even protected properties
via the reflection API so we can make use of that.

Change-Id: Ie143a7bba53060769ff0a868d758f754275768c5
Resolves: #32404
Releases: 1.5

typo3/sysext/extbase/Classes/Object/Container/ClassInfo.php
typo3/sysext/extbase/Classes/Object/Container/ClassInfoFactory.php
typo3/sysext/extbase/Classes/Object/Container/Container.php
typo3/sysext/extbase/Tests/Unit/Fixtures/ClassWithInjectProperties.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Unit/Object/Container/ClassInfoFactoryTest.php

index c43fca6..a1ae30c 100644 (file)
@@ -62,6 +62,14 @@ class Tx_Extbase_Object_Container_ClassInfo {
        private $injectMethods;
 
        /**
+        * All setter injections in the format
+        *      array (<nameOfProperty> => <classNameToInject> )
+        *
+        * @var array
+        */
+       private $injectProperties;
+
+       /**
         * Indicates if the class is a singleton or not.
         *
         * @var boolean
@@ -83,10 +91,11 @@ class Tx_Extbase_Object_Container_ClassInfo {
         * @param boolean $isSingleton
         * @param boolean $isInitializeable
         */
-       public function __construct($className, array $constructorArguments, array $injectMethods, $isSingleton = FALSE, $isInitializeable = FALSE) {
+       public function __construct($className, array $constructorArguments, array $injectMethods, $isSingleton = FALSE, $isInitializeable = FALSE, array $injectProperties = array()) {
                $this->className = $className;
                $this->constructorArguments = $constructorArguments;
                $this->injectMethods = $injectMethods;
+               $this->injectProperties = $injectProperties;
                $this->isSingleton = $isSingleton;
                $this->isInitializeable = $isInitializeable;
        }
@@ -119,6 +128,15 @@ class Tx_Extbase_Object_Container_ClassInfo {
        }
 
        /**
+        * Returns an array with the inject properties
+        *
+        * @return array
+        */
+       public function getInjectProperties() {
+               return $this->injectProperties;
+       }
+
+       /**
         * Asserts if the class is a singleton or not.
         *
         * @return boolean
@@ -144,4 +162,11 @@ class Tx_Extbase_Object_Container_ClassInfo {
        public function hasInjectMethods() {
                return (count($this->injectMethods) > 0);
        }
+
+       /**
+        * @return boolean
+        */
+       public function hasInjectProperties() {
+               return (count($this->injectProperties) > 0);
+       }
 }
\ No newline at end of file
index 570d3f4..08a0be4 100644 (file)
@@ -46,9 +46,10 @@ class Tx_Extbase_Object_Container_ClassInfoFactory {
                }
                $constructorArguments = $this->getConstructorArguments($reflectedClass);
                $injectMethods = $this->getInjectMethods($reflectedClass);
+               $injectProperties = $this->getInjectProperties($reflectedClass);
                $isSingleton = $this->getIsSingleton($className);
                $isInitializeable = $this->getIsInitializeable($className);
-               return new Tx_Extbase_Object_Container_ClassInfo($className, $constructorArguments, $injectMethods, $isSingleton, $isInitializeable);
+               return new Tx_Extbase_Object_Container_ClassInfo($className, $constructorArguments, $injectMethods, $isSingleton, $isInitializeable, $injectProperties);
        }
 
        /**
@@ -111,6 +112,37 @@ class Tx_Extbase_Object_Container_ClassInfoFactory {
        }
 
        /**
+        * Build a list of properties to be injected for the given class.
+        *
+        * @param ReflectionClass $reflectedClass
+        * @return array (nameOfInjectProperty => nameOfClassToBeInjected)
+        */
+       private function getInjectProperties(ReflectionClass $reflectedClass) {
+               $result = array();
+               $reflectionProperties = $reflectedClass->getProperties();
+
+               if (is_array($reflectionProperties)) {
+                       foreach ($reflectionProperties as $reflectionProperty) {
+                               $reflectedProperty = t3lib_div::makeInstance(
+                                       'Tx_Extbase_Reflection_PropertyReflection',
+                                       $reflectedClass->getName(),
+                                       $reflectionProperty->getName()
+                               );
+
+                               if ($reflectedProperty->isTaggedWith('inject')
+                                       && $reflectedProperty->getName() !== 'settings') {
+
+                                       $varValues = $reflectedProperty->getTagValues('var');
+                                       if (count($varValues) == 1) {
+                                               $result[$reflectedProperty->getName()] = $varValues[0];
+                                       }
+                               }
+                       }
+               }
+               return $result;
+       }
+
+       /**
         * This method is used to determin if a class is a singleton or not.
         *
         * @param string $classname
index 70efddb..87d1b47 100644 (file)
@@ -215,7 +215,7 @@ class Tx_Extbase_Object_Container_Container implements t3lib_Singleton {
         * @return void
         */
        protected function injectDependencies($instance, Tx_Extbase_Object_Container_ClassInfo $classInfo) {
-               if (!$classInfo->hasInjectMethods()) return;
+               if (!$classInfo->hasInjectMethods() && !$classInfo->hasInjectProperties()) return;
 
                foreach ($classInfo->getInjectMethods() as $injectMethodName => $classNameToInject) {
 
@@ -226,6 +226,22 @@ class Tx_Extbase_Object_Container_Container implements t3lib_Singleton {
 
                        $instance->$injectMethodName($instanceToInject);
                }
+
+               foreach ($classInfo->getInjectProperties() as $injectPropertyName => $classNameToInject) {
+
+                       $instanceToInject = $this->getInstanceInternal($classNameToInject);
+                       if ($classInfo->getIsSingleton() && !($instanceToInject instanceof t3lib_Singleton)) {
+                               $this->log('The singleton "' . $classInfo->getClassName() . '" needs a prototype in "' . $injectPropertyName . '". This is often a bad code smell; often you rather want to inject a singleton.', 1320177676);
+                       }
+
+                       $propertyReflection = t3lib_div::makeInstance(
+                               'Tx_Extbase_Reflection_PropertyReflection',
+                               $instance,
+                               $injectPropertyName
+                       );
+                       $propertyReflection->setAccessible(TRUE);
+                       $propertyReflection->setValue($instance, $instanceToInject);
+               }
        }
 
        /**
diff --git a/typo3/sysext/extbase/Tests/Unit/Fixtures/ClassWithInjectProperties.php b/typo3/sysext/extbase/Tests/Unit/Fixtures/ClassWithInjectProperties.php
new file mode 100644 (file)
index 0000000..f8837a0
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2011 Felix Oertel, <f@oer.tel>
+*
+*  All rights reserved
+*
+*  This script is part of the TYPO3 project. The TYPO3 project is
+*  free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 3 of the License, or
+*  (at your option) any later version.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*
+*  This script is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * @author Felix Oertel, <f@oer.tel>
+ */
+class Tx_Extbase_Fixture_ClassWithInjectProperties {
+
+       /**
+        * @var Tx_Extbase_Fixture_DummyClass
+        */
+       protected $dummyClass;
+
+       /**
+        * @var Tx_Extbase_Fixture_SecondDummyClass
+        * @inject
+        */
+       protected $secondDummyClass;
+}
+
+?>
\ No newline at end of file
index f82428a..dadd62d 100644 (file)
 ***************************************************************/
 
 require_once(t3lib_extMgm::extPath('extbase') . 'Tests/Unit/Object/Container/Fixtures/Testclasses.php');
+require_once(t3lib_extMgm::extPath('extbase') . 'Tests/Unit/Fixtures/ClassWithInjectProperties.php');
+require_once(t3lib_extMgm::extPath('extbase') . 'Tests/Unit/Fixtures/DummyClass.php');
+require_once(t3lib_extMgm::extPath('extbase') . 'Tests/Unit/Fixtures/SecondDummyClass.php');
+
 
 /**
  * Testcase for class t3lib_object_ClassInfoFactory.
@@ -65,6 +69,14 @@ class Tx_Extbase_Tests_Unit_Object_Container_ClassInfoFactoryTest extends Tx_Ext
        /**
         * @test
         */
+       public function buildClassInfoDetectsPropertiesToInjectByAnnotation() {
+               $classInfo = $this->classInfoFactory->buildClassInfoFromClassName('Tx_Extbase_Fixture_ClassWithInjectProperties');
+               $this->assertEquals(array('secondDummyClass' => 'Tx_Extbase_Fixture_SecondDummyClass'), $classInfo->getInjectProperties());
+       }
+
+       /**
+        * @test
+        */
        public function moreTestsNeedToBeWritten() {
                $this->markTestIncomplete('More tests need to be written!');
        }