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

php 7.2 with zend-session 2.8.3 : ini_set(): A session is active and 'session.use_cookies' is not a valid sessions-related ini setting error #104

Closed
2 tasks done
samsonasik opened this issue Dec 15, 2017 · 14 comments

Comments

@samsonasik
Copy link
Contributor

samsonasik commented Dec 15, 2017

Provide a narrative description of what you are trying to accomplish.

Code to reproduce the issue

use Zend\Authentication\Storage;

class AuthStorage extends Storage\Session
{
    public function __construct() 
   {
        parent::__construct('auth');

        $sessionConfigOptions = [
            'use_cookies'     => true,
            'cookie_httponly' => true,
            'gc_maxlifetime'  => 8000000 ,
            'cookie_lifetime' => 1800,
        ];
        $this->session->getManager()->getConfig()->setOptions($sessionConfigOptions);
    }
}

$authService = new Zend\Authentication\AuthenticationService(new AuthStorage());

Expected results

Working

Actual results

Got errors:

  • ini_set(): A session is active. You cannot change the session module's ini settings at this time
  • Uncaught Zend\Session\Exception\InvalidArgumentException: 'session.use_cookies' is not a valid sessions-related ini setting

Env:
php version: 7.2
zend-session: 2.8.3

@samuel20miglia
Copy link

samuel20miglia commented Jan 7, 2018

I have also this error on my software, but in my case was session.cookie_lifetime

@Julienh
Copy link

Julienh commented Jan 8, 2018

Same error for me.

@weierophinney
Copy link
Member

What I'm seeing are two issues:

  • First, it looks like PHP has changed behavior around when it allows you to alter several session-related php.ini settings. Typically, this should not be an issue, as configuration should happen before the session has started.
  • Second, our code for handling ini_set() related issues is naïve, as it assumes any failure result to be an indication that the setting is not valid.

The root cause of this, of course, is that the session has already started by the time the SessionConfig instance is running. How exactly are you starting the session? When is the SessionManager being created, and how? This may be something you can fix in your own code, which means we can then document a solution.

@samsonasik
Copy link
Contributor Author

The way call is by the Authentication storage above, it is extends Zend\Authentication\Storage\Session, so SessionManager already created there with default behaviour.

@weierophinney
Copy link
Member

I see the problem: Zend\Authentication\Storage\Session creates a Zend\Session\Container instance with a null Zend\Session\Manager instance, which causes it to create one. During instantiation, it also calls on $manager->start(), which starts the session.

Thus, in your code, you're calling setConfig() on the manager instance after the session has started, which is why you're getting the errors.

The solution to this is to create your authentication storage instance via a factory, and have it pass a session manager instance pulled from the container to the storage constructor. You should also have a factory for creating the session manager instance that passes that configuration to it.

@joepsyko
Copy link

joepsyko commented Jan 9, 2018

I have the same issue, @weierophinney can you please be more specific?

@nevenblazic
Copy link

Identical issue like @samsonasik after upgrading from php 7 to php 7.2

@samsonasik
Copy link
Contributor Author

@weirophinney I’m not sure if that can be a real fix, in php <= 7.1, we can create as many container as we want, eg:

use Zend\Session\Container;

$a = new Container("foo");
$b = new Container("bar");

@weierophinney
Copy link
Member

Here's what I meant:

use Zend\Authentication\AuthenticationService;
use Zend\Session\Config\SessionConfig;
use Zend\Session\SessionManager;
use Zend\Session\Storage;

class SessionManagerFactory
{
    public function __invoke(ContainerInterface $container)
    {
        $config = $container->has('config') ? $container->get('config') : [];
        $config = $config['session'] ?? [];

        $sessionConfig = new SessionConfig();
        $sessionConfig->setOptions($config);

        // You could also configure the storage adapter, save handler, and
        // validators here and pass them to the session manager constructor,
        // if desired.
        return new SessionManager($sessionConfig);
    }
}

class AuthStorage extends Storage\Session
{
    public function __construct(SessionManager $manager)
    {
        parent::__construct('auth', null, $manager);
    }
}

class AuthStorageFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new AuthStorage($container->get(SessionManager::class));
    }
}

class AuthenticationServiceFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new AuthenticationService($container->get(AuthStorage::class));
    }
}

// Config:
return [
    'dependencies' => [
        'factories' => [
            AuthenticationService::class => AuthenticationServiceFactory::class,
            AuthStorage::class => AuthStorageFactory::class,
            SessionManager::class => SessionManagerFactory::class,
        ],
    ],
];

// Retrieve the AuthenticationService from the container:
$authService = $container->get(AuthenticationService::class);

@samsonasik — In terms of your last example, the solution to that is to pull the SessionManager from the container, and pass it to the Zend\Session\Container instances you're creating. As an example, if you were using this with a zend-mvc controller:

class FooController extends AbstractActionController
{
    private $session;

    public function __construct(SessionManager $session)
    {
        $this->session = $session;
    }

    public function indexAction()
    {
        $container = new Container('foo', $this->session);
    }
}

Note that the second argument to a Container instance is a Zend\Session\Manager instance, of which SessionManager is an implementation. If you do this, you do not need to worry about the session_start/ini_set synchronization, as it's taken care of by the fact that the session manager is fully configured before you create the containers.

Honestly, there's no way for us to fix this without requiring you pass the session manager instance to containers, which would be a BC break currently.

@samsonasik
Copy link
Contributor Author

is it possible to add handing at Zend\Session\SessionManager itself ? We may have different SessionManagers used across application layers for different purposes.

@weierophinney
Copy link
Member

@samsonasik The problem is that the container calls $manager->start() during instantiation. This is a necessary step, as, in order to initialize itself, it has to see if data related to the container already exists in the session.

That could potentially be moved to the first set/unset/exists operation, but the logic becomes quite brittle.

Another approach is to use AbstractContainer::setDefaultManager() to set a global default session manager instance, and ensure that's called quite early in your application; AbstractContainer (and its extension, Container) will use that if no manager is provided in the constructor.

Again, my point here is that the order of operations is important. The approach you use above will not work under PHP 7.2, no matter what we do, if the manager has already started the session, and, by that point, it has. You need to configure the session first.

@samsonasik
Copy link
Contributor Author

@weierophinney I'm thinking if we can check with session_status() === PHP_SESSION_ACTIVE check, something like https://3v4l.org/9HJMu :

session_start();

if (session_status() === PHP_SESSION_ACTIVE) {
    session_write_close();  // executed
}

ini_set('session.use_cookies', true);
if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start(); // executed
}

$_SESSION['foo'] = 'foo';

var_dump(session_status() === PHP_SESSION_ACTIVE); // true

So, the handling may be at Zend\Session\Config\SessionConfig::setStorageOption() or Zend\Session\Config\StandardConfig::setOptions()

@samsonasik
Copy link
Contributor Author

@weierophinney I've created PR #107 for it

@Arijus
Copy link

Arijus commented Jan 29, 2018

Same error for me with php 7.2

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants