Skip to content

Commit

Permalink
Integrate SessionTrait from atk4/core
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek committed Feb 8, 2022
1 parent 306916d commit d9ae01a
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 4 deletions.
2 changes: 1 addition & 1 deletion demos/_includes/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Atk4\Ui\Demos;

use Atk4\Core\NameTrait;
use Atk4\Core\SessionTrait;
use Atk4\Ui\SessionTrait;

class Session
{
Expand Down
3 changes: 2 additions & 1 deletion demos/interactive/popup.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
62 changes: 62 additions & 0 deletions docs/session.rst
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<groups>
<exclude>
<group>demos_http</group>
<group>require_session</group>
</exclude>
</groups>
<listeners>
Expand Down
143 changes: 143 additions & 0 deletions src/SessionTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

declare(strict_types=1);

namespace Atk4\Ui;

use Atk4\Core\TraitUtil;

trait SessionTrait
{
/** @var string Session container key. */
protected $session_key = '__atk_session';

/**
* Create new session.
*
* @param array $options Options for session_start()
*
* @return $this
*/
public function startSession(array $options = [])
{
// all methods use this method to start session, so we better check
// NameTrait existence here in one place.
if (!TraitUtil::hasNameTrait($this)) {
throw new Exception('Object should have NameTrait applied to use session');
}

switch (session_status()) {
case \PHP_SESSION_DISABLED:
// @codeCoverageIgnoreStart - impossible to test
throw new Exception('Sessions are disabled on server');
// @codeCoverageIgnoreEnd
case \PHP_SESSION_NONE:
session_start($options);

break;
}

return $this;
}

/**
* Destroy existing session.
*
* @return $this
*/
public function destroySession()
{
if (session_status() === \PHP_SESSION_ACTIVE) {
session_destroy();
unset($_SESSION);
}

return $this;
}

/**
* Remember data in object-relevant session data.
*
* @param mixed $value Value
*
* @return mixed $value
*/
public function memorize(string $key, $value)
{
$this->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;
}
}
2 changes: 1 addition & 1 deletion src/Table/Column/FilterModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Wizard.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/
class Wizard extends View
{
use \Atk4\Core\SessionTrait;
use SessionTrait;

public $defaultTemplate = 'wizard.html';
public $ui = 'steps';
Expand Down
136 changes: 136 additions & 0 deletions tests/SessionTraitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php

declare(strict_types=1);

namespace Atk4\Ui\Tests;

use Atk4\Core\NameTrait;
use Atk4\Core\Phpunit\TestCase;
use Atk4\Ui\Exception;
use Atk4\Ui\SessionTrait;

/**
* @group require_session
* @runTestsInSeparateProcesses
*/
class SessionTraitTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

session_abort();
$sessionDir = sys_get_temp_dir() . '/atk4_test__ui__session';
if (!file_exists($sessionDir)) {
mkdir($sessionDir);
}
ini_set('session.save_path', $sessionDir);
}

protected function tearDown(): void
{
session_abort();
$sessionDir = ini_get('session.save_path');
foreach (scandir($sessionDir) as $f) {
if (!in_array($f, ['.', '..'], true)) {
unlink($sessionDir . '/' . $f);
}
}

rmdir($sessionDir);

parent::tearDown();
}

public function testException1(): void
{
// when try to start session without NameTrait
$this->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;
}

0 comments on commit d9ae01a

Please sign in to comment.