Skip to content
This repository has been archived by the owner on Jan 31, 2020. It is now read-only.

Commit

Permalink
Ensure setting save_path considers save_handler
Browse files Browse the repository at this point in the history
The fix in #99, while correct, did not address setting the session.save_path
for non-files save handlers when the save handler is set via
`setOption('save_handler', $value)`. The reason for this was due to the
fact that `setOption()` delegates directly to `setStorageOption()`
instead of the relevant class method; the changes in #99 make that
method a no-op in the case of a save handler option.

What this patch does is two-fold:

- It overrides `setOption()` and has it call `setPhpSaveHandler()` if
  `save_handler` is provided as the `$option` argument. It then returns
  on completion. Otherwise, it delegates to the parent.
- It modifies `setPhpSaveHandler()` to extract the bulk of the logic to
  a new method, `performSaveHandlerUpdate()`. This new method now
  returns the save handler _name_ to store. `setPhpSaveHandler()` then
  sets that name as the `save_handler` value in the `$options` property
  before returning.

I have added a test to verify this behavior based on an example provided
in #98.
  • Loading branch information
weierophinney committed Dec 1, 2017
1 parent 96da246 commit e52b095
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 53 deletions.
153 changes: 102 additions & 51 deletions src/Config/SessionConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ class SessionConfig extends StandardConfig
protected $rememberMeSeconds = 1209600; // 2 weeks

/**
* @var string
* Name of the save handler currently in use. This will either be a PHP
* built-in save handler name, or the name of a SessionHandlerInterface
* class being used as a save handler.
*
* @var null|string
*/
protected $saveHandler;

Expand Down Expand Up @@ -85,6 +89,24 @@ class SessionConfig extends StandardConfig
*/
protected $validHashFunctions;

/**
* Override standard option setting.
*
* Provides an overload for setting the save handler.
*
* {@inheritDoc}
*/
public function setOption($option, $value)
{
switch (strtolower($option)) {
case 'save_handler':
$this->setPhpSaveHandler($value);
return $this;
default:
return parent::setOption($option, $value);
}
}

/**
* Set storage option in backend configuration store
*
Expand Down Expand Up @@ -176,56 +198,8 @@ public function setSaveHandler($phpSaveHandler)
*/
public function setPhpSaveHandler($phpSaveHandler)
{
$knownHandlers = $this->locateRegisteredSaveHandlers();

if (in_array($phpSaveHandler, $knownHandlers, true)) {
set_error_handler([$this, 'handleError']);
session_module_name($phpSaveHandler);
restore_error_handler();
if ($this->phpErrorCode >= E_WARNING) {
throw new Exception\InvalidArgumentException(sprintf(
'Error setting session save handler module "%s": %s',
$phpSaveHandler,
$this->phpErrorMessage
));
}

$this->saveHandler = $phpSaveHandler;
$this->setOption('save_handler', $phpSaveHandler);
return $this;
}

if (is_string($phpSaveHandler)
&& (! class_exists($phpSaveHandler)
|| ! (in_array(SessionHandlerInterface::class, class_implements($phpSaveHandler)))
)
) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid save handler specified ("%s"); must be one of [%s]'
. ' or a class implementing %s',
$phpSaveHandler,
implode(', ', $knownHandlers),
SessionHandlerInterface::class,
SessionHandlerInterface::class
));
}

if (is_string($phpSaveHandler)) {
$phpSaveHandler = new $phpSaveHandler();
}

if (! $phpSaveHandler instanceof SessionHandlerInterface) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid save handler specified ("%s"); must implement %s',
get_class($phpSaveHandler),
SessionHandlerInterface::class
));
}

session_set_save_handler($phpSaveHandler);

$this->saveHandler = get_class($phpSaveHandler);
$this->setOption('save_handler', $this->saveHandler);
$this->saveHandler = $this->performSaveHandlerUpdate($phpSaveHandler);
$this->options['save_handler'] = $this->saveHandler;
return $this;
}

Expand Down Expand Up @@ -429,6 +403,83 @@ private function locateRegisteredSaveHandlers()
return $this->knownSaveHandlers;
}

/**
* Perform a session.save_handler update.
*
* Determines if the save handler represents a PHP built-in
* save handler, and, if so, passes that value to session_module_name
* in order to activate it. The save handler name is then returned.
*
* If it is not, it tests to see if it is a SessionHandlerInterface
* implementation. If the string is a class implementing that interface,
* it creates an instance of it. In such cases, it then calls
* session_set_save_handler to activate it. The class name of the
* handler is returned.
*
* In all other cases, an exception is raised.
*
* @param string|SessionHandlerInterface $phpSaveHandler
* @return string
* @throws Exception\InvalidArgumentException if an error occurs when
* setting a PHP session save handler module.
* @throws Exception\InvalidArgumentException if the $phpSaveHandler
* is a string that does not represent a class implementing
* SessionHandlerInterface.
* @throws Exception\InvalidArgumentException if $phpSaveHandler is
* a non-string value that does not implement SessionHandlerInterface.
*/
private function performSaveHandlerUpdate($phpSaveHandler)
{
$knownHandlers = $this->locateRegisteredSaveHandlers();

if (in_array($phpSaveHandler, $knownHandlers, true)) {
$phpSaveHandler = strtolower($phpSaveHandler);
set_error_handler([$this, 'handleError']);
session_module_name($phpSaveHandler);
restore_error_handler();
if ($this->phpErrorCode >= E_WARNING) {
throw new Exception\InvalidArgumentException(sprintf(
'Error setting session save handler module "%s": %s',
$phpSaveHandler,
$this->phpErrorMessage
));
}

return $phpSaveHandler;
}

if (is_string($phpSaveHandler)
&& (! class_exists($phpSaveHandler)
|| ! (in_array(SessionHandlerInterface::class, class_implements($phpSaveHandler)))
)
) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid save handler specified ("%s"); must be one of [%s]'
. ' or a class implementing %s',
$phpSaveHandler,
implode(', ', $knownHandlers),
SessionHandlerInterface::class,
SessionHandlerInterface::class
));
}

if (is_string($phpSaveHandler)) {
$phpSaveHandler = new $phpSaveHandler();
}

if (! $phpSaveHandler instanceof SessionHandlerInterface) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid save handler specified ("%s"); must implement %s',
get_class($phpSaveHandler),
SessionHandlerInterface::class
));
}

session_set_save_handler($phpSaveHandler);

return get_class($phpSaveHandler);
}

/**
* Grab module information from phpinfo.
*
Expand Down
26 changes: 24 additions & 2 deletions test/Config/SessionConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1196,14 +1196,36 @@ public function testProvidingValidKnownSessionHandlerToSetPhpSaveHandlerResultsI
$phpinfo = $this->getFunctionMock('Zend\Session\Config', 'phpinfo');
$phpinfo
->expects($this->once())
->will($this->returnCallback(function () {
->willReturnCallback(function () {
echo "Registered save handlers => user files unittest";
}));
});

$sessionModuleName = $this->getFunctionMock('Zend\Session\Config', 'session_module_name');
$sessionModuleName->expects($this->once());

$this->assertSame($this->config, $this->config->setPhpSaveHandler('unittest'));
$this->assertEquals('unittest', $this->config->getOption('save_handler'));
}

public function testCanProvidePathWhenUsingRedisSaveHandler()
{
$phpinfo = $this->getFunctionMock('Zend\Session\Config', 'phpinfo');
$phpinfo
->expects($this->once())
->willReturnCallback(function () {
echo "Registered save handlers => user files redis";
});

$sessionModuleName = $this->getFunctionMock('Zend\Session\Config', 'session_module_name');
$sessionModuleName
->expects($this->once())
->with($this->equalTo('redis'));

$url = 'tcp://localhost:6379?auth=foobar&database=1';

$this->config->setOption('save_handler', 'redis');
$this->config->setOption('save_path', $url);

$this->assertSame($url, $this->config->getOption('save_path'));
}
}

0 comments on commit e52b095

Please sign in to comment.