From c9694237d2e59109fc121148e124e0f4139d073e Mon Sep 17 00:00:00 2001 From: Drak Date: Thu, 24 Nov 2011 10:52:08 +0545 Subject: [PATCH] [HttpFoundation] Added FlashBagInterface and concrete implementation. This commit outsources the flash message processing to it's own interface. Overall flash messages now can have multiple flash types and each type can store multiple messages. For convenience there are now four flash types by default, INFO, NOTICE, WARNING and ERROR. There are two concrete implementations: one preserving the old behaviour of flash messages expiring exactly after one page load, regardless of being displayed or not; and the other where flash messages persist until explicitly popped. --- .../HttpFoundation/AutoExpireFlashBag.php | 168 ++++++++++++++++++ .../Component/HttpFoundation/FlashBag.php | 157 ++++++++++++++++ .../HttpFoundation/FlashBagInterface.php | 121 +++++++++++++ .../HttpFoundation/AutoExpireFlashBagTest.php | 152 ++++++++++++++++ .../Component/HttpFoundation/FlashBagTest.php | 148 +++++++++++++++ 5 files changed, 746 insertions(+) create mode 100644 src/Symfony/Component/HttpFoundation/AutoExpireFlashBag.php create mode 100644 src/Symfony/Component/HttpFoundation/FlashBag.php create mode 100644 src/Symfony/Component/HttpFoundation/FlashBagInterface.php create mode 100644 tests/Symfony/Tests/Component/HttpFoundation/AutoExpireFlashBagTest.php create mode 100644 tests/Symfony/Tests/Component/HttpFoundation/FlashBagTest.php diff --git a/src/Symfony/Component/HttpFoundation/AutoExpireFlashBag.php b/src/Symfony/Component/HttpFoundation/AutoExpireFlashBag.php new file mode 100644 index 0000000000..5c435a2821 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/AutoExpireFlashBag.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * AutoExpireFlashBag flash message container. + * + * @author Drak + */ +class AutoExpireFlashBag implements FlashBagInterface +{ + /** + * Flash messages. + * + * @var array + */ + private $flashes = array(); + + /** + * The storage key for flashes in the session + * + * @var string + */ + private $storageKey; + + /** + * Constructor. + * + * @param type $storageKey The key used to store flashes in the session. + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + $this->flashes = array('display' => array(), 'new' => array()); + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + + // The logic: messages from the last request will be stored in new, so we move them to previous + // This request we will show what is in 'display'. What is placed into 'new' this time round will + // be moved to display next time round. + $this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array(); + $this->flashes['new'] = array(); + } + + /** + * {@inheritdoc} + */ + public function add($message, $type = self::NOTICE) + { + $this->flashes['new'][$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function get($type) + { + if (!$this->has($type)) { + return array(); + } + + return $this->flashes['display'][$type]; + } + + /** + * {@inheritdoc} + */ + public function pop($type) + { + if (!$this->has($type)) { + return array(); + } + + return $this->clear($type); + } + + /** + * {@inheritdoc} + */ + public function popAll() + { + return $this->clearAll(); + } + + /** + * {@inheritdoc} + */ + public function set($type, array $array) + { + $this->flashes['new'][$type] = $array; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return array_key_exists('display', $this->flashes) ? (array)$this->flashes['display'] : array(); + } + + /** + * {@inheritdoc} + */ + public function clear($type) + { + $return = array(); + if (isset($this->flashes['new'][$type])) { + unset($this->flashes['new'][$type]); + } + + if (isset($this->flashes['display'][$type])) { + $return = $this->flashes['display'][$type]; + unset($this->flashes['display'][$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function clearAll() + { + $return = $this->flashes['display']; + $this->flashes = array('new' => array(), 'display' => array()); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } +} diff --git a/src/Symfony/Component/HttpFoundation/FlashBag.php b/src/Symfony/Component/HttpFoundation/FlashBag.php new file mode 100644 index 0000000000..89d7dfffc2 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/FlashBag.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * FlashBag flash message container. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface +{ + /** + * Flash messages. + * + * @var array + */ + private $flashes = array(); + + /** + * The storage key for flashes in the session + * + * @var string + */ + private $storageKey; + + /** + * Constructor. + * + * @param type $storageKey The key used to store flashes in the session. + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + } + + /** + * {@inheritdoc} + */ + public function add($message, $type = self::NOTICE) + { + $this->flashes[$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function get($type) + { + if (!$this->has($type)) { + return array(); + } + + return $this->flashes[$type]; + } + + /** + * {@inheritdoc} + */ + public function pop($type) + { + if (!$this->has($type)) { + return array(); + } + + return $this->clear($type); + } + + /** + * {@inheritdoc} + */ + public function popAll() + { + return $this->clearAll(); + } + + /** + * {@inheritdoc} + */ + public function set($type, array $array) + { + $this->flashes[$type] = $array; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes); + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->flashes; + } + + /** + * {@inheritdoc} + */ + public function clear($type) + { + $return = array(); + if (isset($this->flashes[$type])) { + $return = $this->flashes[$type]; + unset($this->flashes[$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function clearAll() + { + $return = $this->flashes; + $this->flashes = array(); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } +} diff --git a/src/Symfony/Component/HttpFoundation/FlashBagInterface.php b/src/Symfony/Component/HttpFoundation/FlashBagInterface.php new file mode 100644 index 0000000000..89ea8e197b --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/FlashBagInterface.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface +{ + const INFO = 'info'; + const NOTICE = 'notice'; + const WARNING = 'warning'; + const ERROR = 'error'; + + /** + * Initializes the FlashBag. + * + * @param array &$flashes + */ + function initialize(array &$flashes); + + /** + * Adds a flash to the stack for a given type. + * + * @param string $message + * @param string $type + */ + function add($message, $type = self::NOTICE); + + /** + * Gets flash messages for a given type. + * + * @param string $type Message category type. + * + * @return array + */ + function get($type); + + /** + * Pops and clears flashes from the stack. + * + * @param string $type + * + * @return array + */ + function pop($type); + + /** + * Pops all flashes from the stacl and clears flashes. + * + * @param string $type + * + * @return array Empty array, or indexed array of arrays. + */ + function popAll(); + + /** + * Sets an array of flash messages for a given type. + * + * @param string $type + * @param array $array + */ + function set($type, array $array); + + /** + * Has flash messages for a given type? + * + * @param string $type + * + * @return boolean + */ + function has($type); + + /** + * Returns a list of all defined types. + * + * @return array + */ + function keys(); + + /** + * Gets all flash messages. + * + * @return array + */ + function all(); + + /** + * Clears flash messages for a given type. + * + * @param string $type + * + * @return array Returns an array of what was just cleared. + */ + function clear($type); + + /** + * Clears all flash messages. + * + * @return array Empty array or indexed arrays or array if none. + */ + function clearAll(); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + function getStorageKey(); +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/AutoExpireFlashBagTest.php b/tests/Symfony/Tests/Component/HttpFoundation/AutoExpireFlashBagTest.php new file mode 100644 index 0000000000..d3c21c6c06 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/AutoExpireFlashBagTest.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\AutoExpireFlashBag as FlashBag; +use Symfony\Component\HttpFoundation\FlashBagInterface; + +/** + * AutoExpireFlashBagTest + * + * @author Drak + */ +class AutoExpireFlashBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\FlashBagInterface + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + public function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array('new' => array(FlashBag::NOTICE => array('A previous flash message'))); + $this->bag->initialize($this->array); + } + + public function tearDown() + { + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $bag = new FlashBag(); + $array = array('new' => array(FlashBag::NOTICE => array('A previous flash message'))); + $bag->initialize($array); + $this->assertEquals(array('A previous flash message'), $bag->get(FlashBag::NOTICE)); + $array = array('new' => array( + FlashBag::NOTICE => array('Something else'), + FlashBag::ERROR => array('a', 'b'), + )); + $bag->initialize($array); + $this->assertEquals(array('Something else'), $bag->get(FlashBag::NOTICE)); + $this->assertEquals(array('a', 'b'), $bag->get(FlashBag::ERROR)); + } + + public function testAdd() + { + $this->bag->add('Something new', FlashBag::NOTICE); + $this->bag->add('Smile, it might work next time', FlashBag::ERROR); + $this->assertEquals(array('A previous flash message'), $this->bag->get(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->bag->get(FlashBag::ERROR)); + } + + public function testGet() + { + $this->assertEquals(array('A previous flash message'), $this->bag->get(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->bag->get('non_existing_type')); + } + + public function testSet() + { + $this->bag->set(FlashBag::NOTICE, array('Foo', 'Bar')); + $this->assertNotEquals(array('Foo', 'Bar'), $this->bag->get(FlashBag::NOTICE)); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + } + + public function testKeys() + { + $this->assertEquals(array(FlashBag::NOTICE), $this->bag->keys()); + } + + public function testAll() + { + $array = array( + 'new' => array( + FlashBag::NOTICE => array('Foo'), + FlashBag::ERROR => array('Bar'), + ), + ); + + $this->bag->initialize($array); + $this->assertEquals(array( + FlashBag::NOTICE => array('Foo'), + FlashBag::ERROR => array('Bar'), + ), $this->bag->all() + ); + } + + public function testPop() + { + $this->assertEquals(array('A previous flash message'), $this->bag->pop(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->bag->pop(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->bag->pop('non_existing_type')); + } + + public function testPopAll() + { + $this->bag->set(FlashBag::NOTICE, array('Foo')); + $this->bag->set(FlashBag::ERROR, array('Bar')); + $this->assertEquals(array( + FlashBag::NOTICE => array('A previous flash message'), + ), $this->bag->popAll() + ); + + $this->assertEquals(array(), $this->bag->popAll()); + } + + public function testClear() + { + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + $this->assertEquals(array('A previous flash message'), $this->bag->clear(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->bag->clear(FlashBag::NOTICE)); + $this->assertFalse($this->bag->has(FlashBag::NOTICE)); + } + + public function testClearAll() + { + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + $this->bag->add('Smile, it might work next time', FlashBag::ERROR); + $this->assertFalse($this->bag->has(FlashBag::ERROR)); + $this->assertEquals(array( + FlashBag::NOTICE => array('A previous flash message'), + ), $this->bag->clearAll() + ); + $this->assertEquals(array(), $this->bag->clearAll()); + $this->assertFalse($this->bag->has(FlashBag::NOTICE)); + $this->assertFalse($this->bag->has(FlashBag::ERROR)); + } + +} diff --git a/tests/Symfony/Tests/Component/HttpFoundation/FlashBagTest.php b/tests/Symfony/Tests/Component/HttpFoundation/FlashBagTest.php new file mode 100644 index 0000000000..0d5a8a1ac5 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/FlashBagTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\FlashBag; +use Symfony\Component\HttpFoundation\FlashBagInterface; + +/** + * FlashBagTest + * + * @author Drak + */ +class FlashBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\FlashBagInterface + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + public function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array(FlashBag::NOTICE => array('A previous flash message')); + $this->bag->initialize($this->array); + } + + public function tearDown() + { + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $bag = new FlashBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $bag->all()); + $array = array('should' => array('change')); + $bag->initialize($array); + $this->assertEquals($array, $bag->all()); + } + + public function testAdd() + { + $this->bag->add('Something new', FlashBag::NOTICE); + $this->bag->add('Smile, it might work next time', FlashBag::ERROR); + $this->assertEquals(array('A previous flash message', 'Something new'), $this->bag->get(FlashBag::NOTICE)); + $this->assertEquals(array('Smile, it might work next time'), $this->bag->get(FlashBag::ERROR)); + } + + public function testGet() + { + $this->assertEquals(array('A previous flash message'), $this->bag->get(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->bag->get('non_existing_type')); + } + + public function testPop() + { + $this->assertEquals(array('A previous flash message'), $this->bag->pop(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->bag->pop(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->bag->pop('non_existing_type')); + } + + public function testPopAll() + { + $this->bag->set(FlashBag::NOTICE, array('Foo')); + $this->bag->set(FlashBag::ERROR, array('Bar')); + $this->assertEquals(array( + FlashBag::NOTICE => array('Foo'), + FlashBag::ERROR => array('Bar')), $this->bag->popAll() + ); + + $this->assertEquals(array(), $this->bag->popAll()); + } + + public function testSet() + { + $this->bag->set(FlashBag::NOTICE, array('Foo', 'Bar')); + $this->assertEquals(array('Foo', 'Bar'), $this->bag->get(FlashBag::NOTICE)); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + } + + public function testKeys() + { + $this->assertEquals(array(FlashBag::NOTICE), $this->bag->keys()); + } + + public function testAll() + { + $this->bag->set(FlashBag::NOTICE, array('Foo')); + $this->bag->set(FlashBag::ERROR, array('Bar')); + $this->assertEquals(array( + FlashBag::NOTICE => array('Foo'), + FlashBag::ERROR => array('Bar')), $this->bag->all() + ); + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + $this->assertTrue($this->bag->has(FlashBag::ERROR)); + $this->assertEquals(array( + FlashBag::NOTICE => array('Foo'), + FlashBag::ERROR => array('Bar'), + ), $this->bag->all() + ); + } + + public function testClear() + { + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + $this->assertEquals(array('A previous flash message'), $this->bag->clear(FlashBag::NOTICE)); + $this->assertEquals(array(), $this->bag->clear(FlashBag::NOTICE)); + $this->assertFalse($this->bag->has(FlashBag::NOTICE)); + } + + public function testClearAll() + { + $this->assertTrue($this->bag->has(FlashBag::NOTICE)); + $this->bag->add('Smile, it might work next time', FlashBag::ERROR); + $this->assertTrue($this->bag->has(FlashBag::ERROR)); + $this->assertEquals(array( + FlashBag::NOTICE => array('A previous flash message'), + FlashBag::ERROR => array('Smile, it might work next time'), + ), $this->bag->clearAll() + ); + $this->assertEquals(array(), $this->bag->clearAll()); + $this->assertFalse($this->bag->has(FlashBag::NOTICE)); + $this->assertFalse($this->bag->has(FlashBag::ERROR)); + } + +}