[FEATURE] Allow signalSlots to modify arguments 46/23146/8
authorFelix Oertel <fo@lightwerk.com>
Sat, 17 Aug 2013 08:36:38 +0000 (10:36 +0200)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Wed, 28 Aug 2013 13:55:26 +0000 (15:55 +0200)
To allow signalSlots to modify the given arguments,
even if not an object (which could be modified by
reference anyway), the signalSlot should be able
to return the arguments.

This will make the signal slot handling diverge
from Flow and violates or at least bends the
signal slot pattern.

To make this clear, we should rename the
signal slot dispatcher in an upcoming change.

Resolves: #51138
Releases: 6.2
Change-Id: I36928adceed672580c76a68891dfb825e43a5a9e
Reviewed-on: https://review.typo3.org/23146
Reviewed-by: Stefan Neufeind
Reviewed-by: Helmut Hummel
Tested-by: Helmut Hummel
Reviewed-by: Anja Leichsenring
Tested-by: Anja Leichsenring
typo3/sysext/extbase/Classes/SignalSlot/Dispatcher.php
typo3/sysext/extbase/Classes/SignalSlot/Exception/InvalidSlotReturnException.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Fixture/SlotFixture.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Unit/SignalSlot/DispatcherTest.php

index d6d79dc..933ced6 100644 (file)
@@ -115,35 +115,54 @@ class Dispatcher implements \TYPO3\CMS\Core\SingletonInterface {
         * @param string $signalClassName Name of the class containing the signal
         * @param string $signalName Name of the signal
         * @param array $signalArguments arguments passed to the signal method
-        * @return void
-        * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException if the slot is not valid
+        * @return mixed
+        * @throws Exception\InvalidSlotException if the slot is not valid
+        * @throws Exception\InvalidSlotReturnException if a slot returns invalid arguments (too few or return value is not an array)
         * @api
         */
        public function dispatch($signalClassName, $signalName, array $signalArguments = array()) {
                $this->initializeObject();
                if (!isset($this->slots[$signalClassName][$signalName])) {
-                       return;
+                       return NULL;
                }
                foreach ($this->slots[$signalClassName][$signalName] as $slotInformation) {
                        if (isset($slotInformation['object'])) {
                                $object = $slotInformation['object'];
                        } else {
                                if (!isset($this->objectManager)) {
-                                       throw new \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException(sprintf('Cannot dispatch %s::%s to class %s. The object manager is not yet available in the Signal Slot Dispatcher and therefore it cannot dispatch classes.', $signalClassName, $signalName, $slotInformation['class']), 1298113624);
+                                       throw new Exception\InvalidSlotException(sprintf('Cannot dispatch %s::%s to class %s. The object manager is not yet available in the Signal Slot Dispatcher and therefore it cannot dispatch classes.', $signalClassName, $signalName, $slotInformation['class']), 1298113624);
                                }
                                if (!$this->objectManager->isRegistered($slotInformation['class'])) {
-                                       throw new \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException('The given class "' . $slotInformation['class'] . '" is not a registered object.', 1245673367);
+                                       throw new Exception\InvalidSlotException('The given class "' . $slotInformation['class'] . '" is not a registered object.', 1245673367);
                                }
                                $object = $this->objectManager->get($slotInformation['class']);
                        }
+
+                       if (!method_exists($object, $slotInformation['method'])) {
+                               throw new Exception\InvalidSlotException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '() does not exist.', 1245673368);
+                       }
+
+                       $preparedSlotArguments = $signalArguments;
                        if ($slotInformation['passSignalInformation'] === TRUE) {
-                               $signalArguments[] = $signalClassName . '::' . $signalName;
+                               $preparedSlotArguments[] = $signalClassName . '::' . $signalName;
                        }
-                       if (!method_exists($object, $slotInformation['method'])) {
-                               throw new \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '() does not exist.', 1245673368);
+
+                       $slotReturn = call_user_func_array(array($object, $slotInformation['method']), $preparedSlotArguments);
+
+                       if ($slotReturn) {
+                               if (!is_array($slotReturn)) {
+                                       throw new Exception\InvalidSlotReturnException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '()\'s return value is of an not allowed type ('
+                                               . gettype($slotReturn) . ').', 1376683067);
+                               } elseif (count($slotReturn) !== count($signalArguments)) {
+                                       throw new Exception\InvalidSlotReturnException('The slot method ' . get_class($object) . '->' . $slotInformation['method'] . '() returned a different number ('
+                                               . count($slotReturn) . ') of arguments, than it recieved (' . count($signalArguments) . ').', 1376683066);
+                               } else {
+                                       $signalArguments = $slotReturn;
+                               }
                        }
-                       call_user_func_array(array($object, $slotInformation['method']), $signalArguments);
                }
+
+               return $signalArguments;
        }
 
        /**
diff --git a/typo3/sysext/extbase/Classes/SignalSlot/Exception/InvalidSlotReturnException.php b/typo3/sysext/extbase/Classes/SignalSlot/Exception/InvalidSlotReturnException.php
new file mode 100644 (file)
index 0000000..8ac57f7
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace TYPO3\CMS\Extbase\SignalSlot\Exception;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2013 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
+ *  Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
+ *  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 2 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.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  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!
+ ***************************************************************/
+
+/**
+ * "Invalid Slot Return" Exception
+ *
+ * @api
+ */
+class InvalidSlotReturnException extends \TYPO3\CMS\Extbase\Object\Exception {
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Tests/Fixture/SlotFixture.php b/typo3/sysext/extbase/Tests/Fixture/SlotFixture.php
new file mode 100644 (file)
index 0000000..2feff90
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace TYPO3\CMS\Extbase\Tests\Fixture;
+
+/*                                                                        *
+ * This script belongs to the Extbase framework.                          *
+ *                                                                        *
+ * It is free software; you can redistribute it and/or modify it under    *
+ * the terms of the GNU Lesser General Public License as published by the *
+ * Free Software Foundation, either version 3 of the License, or (at your *
+ * option) any later version.                                             *
+ *                                                                        *
+ * This script is distributed in the hope that it will be useful, but     *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN-    *
+ * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser       *
+ * General Public License for more details.                               *
+ *                                                                        *
+ * You should have received a copy of the GNU Lesser General Public       *
+ * License along with the script.                                         *
+ * If not, see http://www.gnu.org/licenses/lgpl.html                      *
+ *                                                                        *
+ * The TYPO3 project - inspiring people to share!                         *
+ *                                                                        */
+
+/**
+ * A slot
+ */
+class SlotFixture {
+       public function slot() {}
+}
+
+?>
\ No newline at end of file
index 969ff9d..691a076 100644 (file)
@@ -31,7 +31,6 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\SignalSlot;
  * Testcase for the Signal Dispatcher Class
  *
  * @author Felix Oertel <f@oer.tel>
- * @author Alexander Schnitzler <alex.schnitzler@typovision.de>
  */
 class DispatcherTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
 
@@ -47,7 +46,6 @@ class DispatcherTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
 
        /**
         * @test
-        * @author Felix Oertel <f@oer.tel>
         */
        public function connectAllowsForConnectingASlotWithASignal() {
                $mockSignal = $this->getMock('ClassA', array('emitSomeSignal'));
@@ -61,7 +59,6 @@ class DispatcherTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
 
        /**
         * @test
-        * @author Felix Oertel <f@oer.tel>
         */
        public function connectAlsoAcceptsObjectsInPlaceOfTheClassName() {
                $mockSignal = $this->getMock('ClassA', array('emitSomeSignal'));
@@ -75,7 +72,6 @@ class DispatcherTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
 
        /**
         * @test
-        * @author Felix Oertel <f@oer.tel>
         */
        public function connectAlsoAcceptsClosuresActingAsASlot() {
                $mockSignal = $this->getMock('ClassA', array('emitSomeSignal'));
@@ -90,7 +86,6 @@ class DispatcherTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
 
        /**
         * @test
-        * @author Felix Oertel <f@oer.tel>
         */
        public function dispatchPassesTheSignalArgumentsToTheSlotMethod() {
                $arguments = array();
@@ -104,7 +99,6 @@ class DispatcherTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
 
        /**
         * @test
-        * @author Felix Oertel <f@oer.tel>
         */
        public function dispatchRetrievesSlotInstanceFromTheObjectManagerIfOnlyAClassNameWasSpecified() {
                $slotClassName = uniqid('Mock_');
@@ -122,8 +116,127 @@ class DispatcherTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
 
        /**
         * @test
+        */
+       public function dispatchHandsOverArgumentsReturnedByAFormerSlot() {
+               $this->signalSlotDispatcher->_set('isInitialized', TRUE);
+
+               $firstMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
+               $firstMockSlot->expects($this->once())
+                       ->method('slot')
+                       ->will($this->returnCallback(
+                                               function($foo, $baz) {
+                                                       return array('modified_' . $foo, 'modified_' . $baz);}
+                                       ));
+
+               $secondMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
+               $secondMockSlot->expects($this->once())
+                       ->method('slot')
+                       ->with('modified_bar', 'modified_quux');
+
+
+               $this->signalSlotDispatcher->connect('Foo', 'emitBar', $firstMockSlot, 'slot', FALSE);
+               $this->signalSlotDispatcher->connect('Foo', 'emitBar', $secondMockSlot, 'slot', FALSE);
+
+               $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('bar', 'quux'));
+       }
+
+       /**
+        * @test
+        */
+       public function dispatchHandsOverArgumentsReturnedByAFormerSlotWithoutInterferingWithSignalSlotInformation() {
+               $this->signalSlotDispatcher->_set('isInitialized', TRUE);
+
+               $firstMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
+               $firstMockSlot->expects($this->once())
+                       ->method('slot')
+                       ->will($this->returnCallback(
+                                               function($foo, $baz) {
+                                                       return array('modified_' . $foo, 'modified_' . $baz);}
+                                       ));
+
+               $secondMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
+               $secondMockSlot->expects($this->once())
+                       ->method('slot')
+                       ->with('modified_bar', 'modified_quux');
+
+               $this->signalSlotDispatcher->connect('Foo', 'emitBar', $firstMockSlot, 'slot');
+               $this->signalSlotDispatcher->connect('Foo', 'emitBar', $secondMockSlot, 'slot');
+
+               $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('bar', 'quux'));
+       }
+
+       /**
+        * @test
+        */
+       public function dispatchHandsOverFormerArgumentsIfPreviousSlotDoesNotReturnAnything() {
+               $this->signalSlotDispatcher->_set('isInitialized', TRUE);
+
+               $firstMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
+               $firstMockSlot->expects($this->once())
+                       ->method('slot')
+                       ->will($this->returnCallback(
+                                               function($foo, $baz) {
+                                                       return array('modified_' . $foo, 'modified_' . $baz);}
+                                       ));
+
+               $secondMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
+               $secondMockSlot->expects($this->once())
+                       ->method('slot');
+
+               $thirdMockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
+               $thirdMockSlot->expects($this->once())
+                       ->method('slot')
+                       ->with('modified_bar', 'modified_quux');
+
+
+               $this->signalSlotDispatcher->connect('Foo', 'emitBar', $firstMockSlot, 'slot');
+               $this->signalSlotDispatcher->connect('Foo', 'emitBar', $secondMockSlot, 'slot');
+               $this->signalSlotDispatcher->connect('Foo', 'emitBar', $thirdMockSlot, 'slot');
+
+               $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('bar', 'quux'));
+       }
+
+       /**
+        * @test
+        * @expectedException \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException
+        */
+       public function dispatchThrowsAnExceptionIfTheSlotReturnsNonArray() {
+               $this->signalSlotDispatcher->_set('isInitialized', TRUE);
+
+               $mockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
+               $mockSlot->expects($this->once())
+                       ->method('slot')
+                       ->will($this->returnCallback(
+                                               function() {
+                                                       return 'string';}
+                                       ));
+
+               $this->signalSlotDispatcher->connect('Foo', 'emitBar', $mockSlot, 'slot', FALSE);
+               $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('foo' => 'bar', 'baz' => 'quux'));
+       }
+
+       /**
+        * @test
+        * @expectedException \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException
+        */
+       public function dispatchThrowsAnExceptionIfTheSlotReturnsDifferentNumberOfItems() {
+               $this->signalSlotDispatcher->_set('isInitialized', TRUE);
+
+               $mockSlot = $this->getMock('TYPO3\\CMS\\Extbase\\Tests\\Fixture\\SlotFixture');
+               $mockSlot->expects($this->once())
+                       ->method('slot')
+                       ->will($this->returnCallback(
+                                               function() {
+                                                       return array(1, 2, 3);}
+                                       ));
+
+               $this->signalSlotDispatcher->connect('Foo', 'emitBar', $mockSlot, 'slot', FALSE);
+               $this->signalSlotDispatcher->dispatch('Foo', 'emitBar', array('bar', 'quux'));
+       }
+
+       /**
+        * @test
         * @expectedException \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException
-        * @author Felix Oertel <f@oer.tel>
         */
        public function dispatchThrowsAnExceptionIfTheSpecifiedClassOfASlotIsUnknown() {
                $mockObjectManager = $this->getMock('TYPO3\\CMS\\Extbase\\Object\\ObjectManagerInterface');
@@ -137,7 +250,6 @@ class DispatcherTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
        /**
         * @test
         * @expectedException \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException
-        * @author Felix Oertel <f@oer.tel>
         */
        public function dispatchThrowsAnExceptionIfTheSpecifiedSlotMethodDoesNotExist() {
                $slotClassName = uniqid('Mock_');
@@ -155,7 +267,6 @@ class DispatcherTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
 
        /**
         * @test
-        * @author Felix Oertel <f@oer.tel>
         */
        public function dispatchPassesFirstArgumentContainingSlotInformationIfTheConnectionStatesSo() {
                $arguments = array();