From d9ae01aaef3123f43fbbd9aadeee30dcbd1efbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Tue, 8 Feb 2022 13:46:08 +0100 Subject: [PATCH] Integrate SessionTrait from atk4/core --- demos/_includes/Session.php | 2 +- demos/interactive/popup.php | 3 +- docs/session.rst | 62 ++++++++++++++ phpunit.xml.dist | 1 + src/SessionTrait.php | 143 +++++++++++++++++++++++++++++++ src/Table/Column/FilterModel.php | 2 +- src/Wizard.php | 2 +- tests/SessionTraitTest.php | 136 +++++++++++++++++++++++++++++ 8 files changed, 347 insertions(+), 4 deletions(-) create mode 100644 docs/session.rst create mode 100644 src/SessionTrait.php create mode 100644 tests/SessionTraitTest.php diff --git a/demos/_includes/Session.php b/demos/_includes/Session.php index 8fa9ad2605..fab1b6affa 100644 --- a/demos/_includes/Session.php +++ b/demos/_includes/Session.php @@ -5,7 +5,7 @@ namespace Atk4\Ui\Demos; use Atk4\Core\NameTrait; -use Atk4\Core\SessionTrait; +use Atk4\Ui\SessionTrait; class Session { diff --git a/demos/interactive/popup.php b/demos/interactive/popup.php index e17f94e48e..5036e50082 100644 --- a/demos/interactive/popup.php +++ b/demos/interactive/popup.php @@ -20,7 +20,8 @@ /** @var \Atk4\Ui\Lister $cartClass */ $cartClass = AnonymousClassNameCache::get_class(fn () => new class() extends \Atk4\Ui\Lister { - use \Atk4\Core\SessionTrait; + use \Atk4\Ui\SessionTrait; + public $items = []; public $defaultTemplate = 'lister.html'; diff --git a/docs/session.rst b/docs/session.rst new file mode 100644 index 0000000000..de5dde6ad7 --- /dev/null +++ b/docs/session.rst @@ -0,0 +1,62 @@ +============= +Session Trait +============= + +.. php:trait:: SessionTrait + + +Introduction +============ + +SessionTrait is a simple way to let object store relevant data in the session. Specifically used in ATK UI +some objects want to memorize data. (see https://github.com/atk4/ui/blob/develop/src/Wizard.php#L12) + +You would need 3 things. First make use of session trait:: + + use \Atk4\Ui\SessionTrait; + +next you may memorize any value, which will be stored independently from any other object (even of a same class):: + + $this->memorize('dsn', $dsn); + +Later when you need the value, you can simply recall it:: + + $dsn = $this->recall('dsn'); + + +Properties +========== + +.. php:attr:: session_key + + Internal property to make sure that all session data will be stored in one + "container" (array key). + +Methods +======= + +.. php:method:: startSession($options = []) + + Create new session. + +.. php:method:: destroySession() + + Destroy existing session. + +.. php:method:: memorize($key, $value) + + Remember data in object-relevant session data. + +.. php:method:: learn($key, $default = null) + + Similar to memorize, but if value for key exist, will return it. + +.. php:method:: recall($key, $default = null) + + Returns session data for this object. If not previously set, then $default + is returned. + +.. php:method:: forget($key = null) + + Forget session data for arg $key. If $key is omitted will forget all + associated session data. diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d59efca385..60055a9dde 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,6 +13,7 @@ demos_http + require_session diff --git a/src/SessionTrait.php b/src/SessionTrait.php new file mode 100644 index 0000000000..7c37723603 --- /dev/null +++ b/src/SessionTrait.php @@ -0,0 +1,143 @@ +startSession(); + + $_SESSION[$this->session_key][$this->name][$key] = $value; + + return $value; + } + + /** + * Similar to memorize, but if value for key exist, will return it. + * + * @param mixed $default + * + * @return mixed Previously memorized data or $default + */ + public function learn(string $key, $default = null) + { + $this->startSession(); + + if (!isset($_SESSION[$this->session_key][$this->name][$key]) + || $_SESSION[$this->session_key][$this->name][$key] === null + ) { + if ($default instanceof \Closure) { + $default = $default($key); + } + + return $this->memorize($key, $default); + } + + return $this->recall($key); + } + + /** + * Returns session data for this object. If not previously set, then + * $default is returned. + * + * @param mixed $default + * + * @return mixed Previously memorized data or $default + */ + public function recall(string $key, $default = null) + { + $this->startSession(); + + if (!isset($_SESSION[$this->session_key][$this->name][$key]) + || $_SESSION[$this->session_key][$this->name][$key] === null + ) { + if ($default instanceof \Closure) { + $default = $default($key); + } + + return $default; + } + + return $_SESSION[$this->session_key][$this->name][$key]; + } + + /** + * Forget session data for $key. If $key is omitted will forget all + * associated session data. + * + * @param string $key Optional key of data to forget + * + * @return $this + */ + public function forget(string $key = null) + { + $this->startSession(); + + if ($key === null) { + unset($_SESSION[$this->session_key][$this->name]); + } else { + unset($_SESSION[$this->session_key][$this->name][$key]); + } + + return $this; + } +} diff --git a/src/Table/Column/FilterModel.php b/src/Table/Column/FilterModel.php index d19256a675..8236d3bd19 100644 --- a/src/Table/Column/FilterModel.php +++ b/src/Table/Column/FilterModel.php @@ -5,11 +5,11 @@ namespace Atk4\Ui\Table\Column; use Atk4\Core\NameTrait; -use Atk4\Core\SessionTrait; use Atk4\Data\Field; use Atk4\Data\Model; use Atk4\Data\Persistence; use Atk4\Data\Types\Types as CustomTypes; +use Atk4\Ui\SessionTrait; use Doctrine\DBAL\Types\Types; /** diff --git a/src/Wizard.php b/src/Wizard.php index f881436b0f..6bab78d541 100644 --- a/src/Wizard.php +++ b/src/Wizard.php @@ -11,7 +11,7 @@ */ class Wizard extends View { - use \Atk4\Core\SessionTrait; + use SessionTrait; public $defaultTemplate = 'wizard.html'; public $ui = 'steps'; diff --git a/tests/SessionTraitTest.php b/tests/SessionTraitTest.php new file mode 100644 index 0000000000..ee4451ab07 --- /dev/null +++ b/tests/SessionTraitTest.php @@ -0,0 +1,136 @@ +expectException(Exception::class); + $m = new SessionWithoutNameMock(); + $m->startSession(); + } + + public function testConstructor(): void + { + $m = new SessionMock(); + + $this->assertFalse(isset($_SESSION)); + $m->startSession(); + $this->assertTrue(isset($_SESSION)); + $m->destroySession(); + $this->assertFalse(isset($_SESSION)); + } + + /** + * Test memorize(). + */ + public function testMemorize(): void + { + $m = new SessionMock(); + $m->name = 'test'; + + // value as string + $m->memorize('foo', 'bar'); + $this->assertSame('bar', $_SESSION['__atk_session'][$m->name]['foo']); + + // value as null + $m->memorize('foo', null); + $this->assertNull($_SESSION['__atk_session'][$m->name]['foo']); + + // value as object + $o = new \stdClass(); + $m->memorize('foo', $o); + $this->assertSame($o, $_SESSION['__atk_session'][$m->name]['foo']); + + $m->destroySession(); + } + + /** + * Test learn(), recall(), forget(). + */ + public function testLearnRecallForget(): void + { + $m = new SessionMock(); + $m->name = 'test'; + + // value as string + $m->learn('foo', 'bar'); + $this->assertSame('bar', $m->recall('foo')); + + $m->learn('foo', 'qwerty'); + $this->assertSame('bar', $m->recall('foo')); + + $m->forget('foo'); + $this->assertSame('undefined', $m->recall('foo', 'undefined')); + + // value as callback + $m->learn('foo', function ($key) { + return $key . '_bar'; + }); + $this->assertSame('foo_bar', $m->recall('foo')); + + $m->learn('foo_2', 'another'); + $this->assertSame('another', $m->recall('foo_2')); + + $v = $m->recall('foo_3', function ($key) { + return $key . '_bar'; + }); + $this->assertSame('foo_3_bar', $v); + $this->assertSame('undefined', $m->recall('foo_3', 'undefined')); + + $m->forget(); + $this->assertSame('undefined', $m->recall('foo', 'undefined')); + $this->assertSame('undefined', $m->recall('foo_2', 'undefined')); + $this->assertSame('undefined', $m->recall('foo_3', 'undefined')); + } +} + +class SessionMock +{ + use NameTrait; + use SessionTrait; +} +class SessionWithoutNameMock +{ + use SessionTrait; +}