0

I'm trying to override the Magento\Framework\Session\SessionManager class with a preference tag, but it's not working. I've tried to put the tag in the di.xml file in my module and try to put it in /app/etc/di.xml.

Here's my di.xml file:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Framework\Session\SessionManager" type="VENDOR\MODULE\SessionManager" />
</config>

My VENDOR\MODULE\SessionManager:

namespace VENDOR\MODULE;

class SessionManager implements \Magento\Framework\Session\SessionManagerInterface
{
    /**
     * Session destroyed threshold in seconds
     */
    const SESSION_DESTROYED_THRESHOLD = 300;

/**
 * Default options when a call destroy()
 *
 * Description:
 * - send_expire_cookie: whether or not to send a cookie expiring the current session cookie
 * - clear_storage: whether or not to empty the storage object of any stored values
 *
 * @var array
 */
protected $defaultDestroyOptions = ['send_expire_cookie' => TRUE, 'clear_storage' => TRUE];

/**
 * URL host cache
 *
 * @var array
 */
protected static $urlHostCache = [];

/**
 * Validator
 *
 * @var \Magento\Framework\Session\ValidatorInterface
 */
protected $validator;

/**
 * Request
 *
 * @var \Magento\Framework\App\Request\Http
 */
protected $request;

/**
 * SID resolver
 *
 * @var \Magento\Framework\Session\SidResolverInterface
 */
protected $sidResolver;

/**
 * Session config
 *
 * @var \Magento\Framework\Session\Config\ConfigInterface
 */
protected $sessionConfig;

/**
 * Save handler
 *
 * @var \Magento\Framework\Session\SaveHandlerInterface
 */
protected $saveHandler;

/**
 * Storage
 *
 * @var \Magento\Framework\Session\StorageInterface
 */
protected $storage;

/**
 * Cookie Manager
 *
 * @var \Magento\Framework\Stdlib\CookieManagerInterface
 */
protected $cookieManager;

/**
 * @var \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory
 */
protected $cookieMetadataFactory;

/**
 * @var \Magento\Framework\App\State
 */
private $appState;

/**
 * @param \Magento\Framework\App\Request\Http                    $request
 * @param \Magento\Framework\Session\SidResolverInterface        $sidResolver
 * @param \Magento\Framework\Session\Config\ConfigInterface      $sessionConfig
 * @param \Magento\Framework\Session\SaveHandlerInterface        $saveHandler
 * @param \Magento\Framework\Session\ValidatorInterface          $validator
 * @param \Magento\Framework\Session\StorageInterface            $storage
 * @param \Magento\Framework\Stdlib\CookieManagerInterface       $cookieManager
 * @param \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory
 * @param \Magento\Framework\App\State                           $appState
 * @throws \Magento\Framework\Exception\SessionException
 */
public function __construct(
    \Magento\Framework\App\Request\Http $request,
    \Magento\Framework\Session\SidResolverInterface $sidResolver,
    \Magento\Framework\Session\Config\ConfigInterface $sessionConfig,
    \Magento\Framework\Session\SaveHandlerInterface $saveHandler,
    \Magento\Framework\Session\ValidatorInterface $validator,
    \Magento\Framework\Session\StorageInterface $storage,
    \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager,
    \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory,
    \Magento\Framework\App\State $appState
)
{
    $this->request = $request;
    $this->sidResolver = $sidResolver;
    $this->sessionConfig = $sessionConfig;
    $this->saveHandler = $saveHandler;
    $this->validator = $validator;
    $this->storage = $storage;
    $this->cookieManager = $cookieManager;
    $this->cookieMetadataFactory = $cookieMetadataFactory;
    $this->appState = $appState;

    // I put a var_dump to test the script
    var_dump('test');
    die();
    // Enable session.use_only_cookies
    ini_set('session.use_only_cookies', '1');
    $this->start();
}

/**
 * This method needs to support sessions with APC enabled
 * @return void
 */
public function writeClose()
{
    session_write_close();
}

/**
 * Storage accessor method
 *
 * @param string $method
 * @param array  $args
 * @return mixed
 * @throws \InvalidArgumentException
 */
public function __call($method, $args)
{
    if (!\in_array(substr($method, 0, 3), ['get', 'set', 'uns', 'has'])) {
        throw new \InvalidArgumentException(
            sprintf('Invalid method %s::%s(%s)', \get_class($this), $method, print_r($args, 1))
        );
    }
    $return = \call_user_func_array([$this->storage, $method], $args);
    return $return === $this->storage ? $this : $return;
}

/**
 * Configure session handler and start session
 *
 * @throws \Magento\Framework\Exception\SessionException
 * @return $this
 */
public function start()
{

    if (!$this->isSessionExists()) {
        \Magento\Framework\Profiler::start('session_start');

        try {
            $this->appState->getAreaCode();
        } catch (\Magento\Framework\Exception\LocalizedException $e) {
            throw new \Magento\Framework\Exception\SessionException(
                new \Magento\Framework\Phrase(
                    'Area code not set: Area code must be set before starting a session.'
                ),
                $e
            );
        }

        // Need to apply the config options so they can be ready by session_start
        $this->initIniOptions();
        $this->registerSaveHandler();
        if (isset($_SESSION['new_session_id'])) {
            // Not fully expired yet. Could be lost cookie by unstable network.
            session_write_close();
            session_id($_SESSION['new_session_id']);
        }
        $sid = $this->sidResolver->getSid($this);
        // potential custom logic for session id (ex. switching between hosts)
        $this->setSessionId($sid);
        session_start();
        if (isset($_SESSION['destroyed']) && $_SESSION['destroyed'] < time() - self::SESSION_DESTROYED_THRESHOLD) {
            $this->destroy(['clear_storage' => TRUE]);
        }
        $this->validator->validate($this);
        try {
            $this->renewCookie($sid);
        } catch (\Magento\Framework\Exception\InputException $e) {
        } catch (\Magento\Framework\Stdlib\Cookie\CookieSizeLimitReachedException $e) {
        } catch (\Magento\Framework\Stdlib\Cookie\FailureToSendException $e) {
        }

        register_shutdown_function([$this, 'writeClose']);

        $this->_addHost();
        \Magento\Framework\Profiler::stop('session_start');
    }
    $this->storage->init($_SESSION ?? []);
    return $this;
}

/**
 * Renew session cookie to prolong session
 *
 * @param null|string $sid If we have session id we need to use it instead of old cookie value
 * @return $this
 * @throws \Magento\Framework\Exception\InputException
 * @throws \Magento\Framework\Stdlib\Cookie\CookieSizeLimitReachedException
 * @throws \Magento\Framework\Stdlib\Cookie\FailureToSendException
 */
private function renewCookie($sid)
{
    if (!$this->getCookieLifetime()) {
        return $this;
    }
    //When we renew cookie, we should aware, that any other session client do not
    //change cookie too
    $cookieValue = $sid ?: $this->cookieManager->getCookie($this->getName());
    if ($cookieValue) {
        $metadata = $this->cookieMetadataFactory->createPublicCookieMetadata();
        $metadata->setPath($this->sessionConfig->getCookiePath());
        $metadata->setDomain($this->sessionConfig->getCookieDomain());
        $metadata->setDuration($this->sessionConfig->getCookieLifetime());
        $metadata->setSecure($this->sessionConfig->getCookieSecure());
        $metadata->setHttpOnly($this->sessionConfig->getCookieHttpOnly());

        $this->cookieManager->setPublicCookie(
            $this->getName(),
            $cookieValue,
            $metadata
        );
    }

    return $this;
}

/**
 * Register save handler
 *
 * @return bool
 */
protected function registerSaveHandler()
{
    return session_set_save_handler(
        [$this->saveHandler, 'open'],
        [$this->saveHandler, 'close'],
        [$this->saveHandler, 'read'],
        [$this->saveHandler, 'write'],
        [$this->saveHandler, 'destroy'],
        [$this->saveHandler, 'gc']
    );
}

/**
 * Does a session exist
 *
 * @return bool
 */
public function isSessionExists()
{
    return !(session_status() === PHP_SESSION_NONE && !headers_sent());
}

/**
 * Additional get data with clear mode
 *
 * @param string $key
 * @param bool   $clear
 * @return mixed
 */
public function getData($key = '', $clear = FALSE)
{
    $data = $this->storage->getData($key);
    if ($clear && isset($data)) {
        $this->storage->unsetData($key);
    }
    return $data;
}

/**
 * Retrieve session Id
 *
 * @return string
 */
public function getSessionId()
{
    return session_id();
}

/**
 * Retrieve session name
 *
 * @return string
 */
public function getName()
{
    return session_name();
}

/**
 * Set session name
 *
 * @param string $name
 * @return $this
 */
public function setName($name)
{
    session_name($name);
    return $this;
}

/**
 * Destroy/end a session
 *
 * @param  array $options
 * @return void
 */
public function destroy(array $options = NULL)
{
    $options = $options ?? [];
    $options = array_merge($this->defaultDestroyOptions, $options);

    if ($options['clear_storage']) {
        $this->clearStorage();
    }

    if (session_status() !== PHP_SESSION_ACTIVE) {
        return;
    }

    session_regenerate_id(TRUE);
    session_destroy();
    if ($options['send_expire_cookie']) {
        try {
            $this->expireSessionCookie();
        } catch (\Magento\Framework\Exception\InputException $e) {
        } catch (\Magento\Framework\Stdlib\Cookie\FailureToSendException $e) {
        }
    }
}

/**
 * Unset all session data
 *
 * @return $this
 */
public function clearStorage()
{
    $this->storage->unsetData();
    return $this;
}

/**
 * Retrieve Cookie domain
 *
 * @return string
 */
public function getCookieDomain()
{
    return $this->sessionConfig->getCookieDomain();
}

/**
 * Retrieve cookie path
 *
 * @return string
 */
public function getCookiePath()
{
    return $this->sessionConfig->getCookiePath();
}

/**
 * Retrieve cookie lifetime
 *
 * @return int
 */
public function getCookieLifetime()
{
    return $this->sessionConfig->getCookieLifetime();
}

/**
 * Specify session identifier
 *
 * @param   string|null $sessionId
 * @return  $this
 */
public function setSessionId($sessionId)
{
    $this->_addHost();
    if ($sessionId !== NULL && preg_match('#^[0-9a-zA-Z,-]+$#', $sessionId)) {
        session_id($sessionId);
    }
    return $this;
}

/**
 * If session cookie is not applicable due to host or path mismatch - add session id to query
 *
 * @param string $urlHost can be host or url
 * @return string {session_id_key}={session_id_encrypted}
 * @SuppressWarnings(PHPMD.NPathComplexity)
 */
public function getSessionIdForHost($urlHost)
{
    $httpHost = $this->request->getHttpHost();
    if (!$httpHost) {
        return '';
    }

    $urlHostArr = explode('/', $urlHost, 4);
    if (!empty($urlHostArr[2])) {
        $urlHost = $urlHostArr[2];
    }
    $urlPath = empty($urlHostArr[3]) ? '' : $urlHostArr[3];

    if (!isset(self::$urlHostCache[$urlHost])) {
        $urlHostArr = explode(':', $urlHost);
        $urlHost = $urlHostArr[0];
        $sessionId = $httpHost !== $urlHost && !$this->isValidForHost($urlHost) ? $this->getSessionId() : '';
        self::$urlHostCache[$urlHost] = $sessionId;
    }

    return $this->isValidForPath($urlPath) ? self::$urlHostCache[$urlHost] : $this->getSessionId();
}

/**
 * Check if session is valid for given hostname
 *
 * @param string $host
 * @return bool
 */
public function isValidForHost($host)
{
    $hostArr = explode(':', $host);
    $hosts = $this->_getHosts();
    return !empty($hosts[$hostArr[0]]);
}

/**
 * Check if session is valid for given path
 *
 * @param string $path
 * @return bool
 */
public function isValidForPath($path)
{
    $cookiePath = trim($this->getCookiePath(), '/') . '/';
    if ($cookiePath === '/') {
        return TRUE;
    }

    $urlPath = trim($path, '/') . '/';
    return strpos($urlPath, $cookiePath) === 0;
}

/**
 * Register request host name as used with session
 *
 * @return $this
 */
protected function _addHost()
{
    $host = $this->request->getHttpHost();
    if (!$host) {
        return $this;
    }

    $hosts = $this->_getHosts();
    $hosts[$host] = TRUE;
    $_SESSION[self::HOST_KEY] = $hosts;
    return $this;
}

/**
 * Get all host names where session was used
 *
 * @return array
 */
protected function _getHosts()
{
    return $_SESSION[self::HOST_KEY] ?? [];
}

/**
 * Clean all host names that were registered with session
 *
 * @return $this
 */
protected function _cleanHosts()
{
    unset($_SESSION[self::HOST_KEY]);
    return $this;
}

/**
 * Renew session id and update session cookie
 *
 * @return $this
 */
public function regenerateId()
{
    if (headers_sent()) {
        return $this;
    }

    // @codingStandardsIgnoreStart
    if ($this->isSessionExists()) {
        // Regenerate the session
        session_regenerate_id();
        $newSessionId = session_id();
        $_SESSION['new_session_id'] = $newSessionId;
        // Set destroy timestamp
        $_SESSION['destroyed'] = time();
        // Write and close current session;
        session_write_close();
        // Called after destroy()
        $oldSession = $_SESSION;
        // Start session with new session ID
        session_id($newSessionId);
        session_start();
        $_SESSION = $oldSession;
        // New session does not need them
        unset($_SESSION['destroyed'], $_SESSION['new_session_id']);
    } else {
        session_start();
    }
    // @codingStandardsIgnoreEnd
    $this->storage->init($_SESSION ?? []);

    if ($this->sessionConfig->getUseCookies()) {
        try {
            $this->clearSubDomainSessionCookie();
        } catch (\Magento\Framework\Exception\InputException $e) {
        } catch (\Magento\Framework\Stdlib\Cookie\FailureToSendException $e) {
        }
    }
    return $this;
}

/**
 * Expire the session cookie for sub domains
 *
 * @return void
 * @throws \Magento\Framework\Exception\InputException
 * @throws \Magento\Framework\Stdlib\Cookie\FailureToSendException
 */
protected function clearSubDomainSessionCookie()
{
    foreach (array_keys($this->_getHosts()) as $host) {
        // Delete cookies with the same name for parent domains
        if (strpos($this->sessionConfig->getCookieDomain(), $host) > 0) {
            $metadata = $this->cookieMetadataFactory->createPublicCookieMetadata();
            $metadata->setPath($this->sessionConfig->getCookiePath());
            $metadata->setDomain($host);
            $metadata->setSecure($this->sessionConfig->getCookieSecure());
            $metadata->setHttpOnly($this->sessionConfig->getCookieHttpOnly());
            $this->cookieManager->deleteCookie($this->getName(), $metadata);
        }
    }
}

/**
 * Expire the session cookie
 *
 * Sends a session cookie with no value, and with an expiry in the past.
 *
 * @return void
 * @throws \Magento\Framework\Exception\InputException
 * @throws \Magento\Framework\Stdlib\Cookie\FailureToSendException
 */
public function expireSessionCookie()
{
    if (!$this->sessionConfig->getUseCookies()) {
        return;
    }

    $metadata = $this->cookieMetadataFactory->createPublicCookieMetadata();
    $metadata->setPath($this->sessionConfig->getCookiePath());
    $metadata->setDomain($this->sessionConfig->getCookieDomain());
    $metadata->setSecure($this->sessionConfig->getCookieSecure());
    $metadata->setHttpOnly($this->sessionConfig->getCookieHttpOnly());
    $this->cookieManager->deleteCookie($this->getName(), $metadata);
    $this->clearSubDomainSessionCookie();
}

/**
 * Performs ini_set for all of the config options so they can be read by session_start
 *
 * @return void
 */
private function initIniOptions()
{
    foreach ($this->sessionConfig->getOptions() as $option => $value) {
        $result = ini_set($option, $value);
        if ($result === FALSE) {
            $error = error_get_last();
            throw new \InvalidArgumentException(
                sprintf('Failed to set ini option "%s" to value "%s". %s', $option, $value, $error['message'])
            );
        }
    }
}

}

Any suggestions?

5
  • Can you specify what do you want to do with this override? You may not need an overwrite.
    – raumatbel
    Jul 6, 2018 at 17:11
  • It's to fix this issue mention there: github.com/magento/magento2/pull/14973 The script is working, I've tried it in the core and it worked fine, but I need to do it in app>code
    – rparent
    Jul 6, 2018 at 17:26
  • Can you write the class VENDOR\MODULE\SessionManager.php in the question?
    – raumatbel
    Jul 6, 2018 at 18:03
  • Here, I edited my question
    – rparent
    Jul 6, 2018 at 19:08
  • do you have any solution yet?
    – fudu
    Oct 12, 2018 at 6:18

2 Answers 2

1

If you think the PR is correct, you can apply that patch with this module. https://github.com/cweagans/composer-patches

To retrieve the patch of the PR, it is the URL ending .patch

https://github.com/magento/magento2/pull/14973.patch

The idea is:

  1. Install the new module
  2. Download the patch
  3. Upload it to local folder
  4. Update the configuration in composer.json as the doc of the module indicate.
  5. Apply the patch.

Magento documentation on this approach can be found here:

https://support.magento.com/hc/en-us/articles/360005484154-Create-a-patch-for-a-Magento-2-Composer-installation-from-a-GitHub-commit

0

My solution is based on Git command:
First, you create 2 .patch file in patches folder in magento root folder.
For example: My project named "magento_project". So the .patch file should be created in "htdocs/magento_project/patches/sessionmanager.patch"
Here is my patch file i've created for magento 2.1.10 to fix "empty cart" issue ( check the link to know how to fix "empty cart" issue)
https://github.com/magento/magento2/issues/12362

C:\xampp\htdocs\magento_project\patches\sessionmanager.patch

--- a/vendor/magento/framework/Session/SessionManager.php
+++ b/vendor/magento/framework/Session/SessionManager.php
@@ -468,12 +468,23 @@
         if ($this->isSessionExists()) {
-            $oldSessionId = session_id();
-            session_regenerate_id();
-            $newSessionId = session_id();
-            session_id($oldSessionIds);
-            session_destroy();
-
-            $oldSession = $_SESSION;
-            session_id($newSessionId);
-            session_start();
-            $_SESSION = $oldSession;
+            $oldSessionId = session_id();
+            session_regenerate_id();   //regen the session
+            $new_session_id = session_id();
+
+            $_SESSION['new_session_id'] = $new_session_id;
+
+            // Set destroy timestamp
+            $_SESSION['destroyed'] = time();
+
+            // Write and close current session;
+            session_commit();
+            $oldSession = $_SESSION;   //called after destroy - see destroy!
+            // Start session with new session ID
+            session_id($new_session_id);
+            ini_set('session.use_strict_mode', 0);
+            session_start();
+            ini_set('session.use_strict_mode', 1);
+            $_SESSION = $oldSession;
+            // New session does not need them
+            unset($_SESSION['destroyed']);
+            unset($_SESSION['new_session_id']);
         } else {
\ No newline at end of file

C:\xampp\htdocs\magento_project\patches\sessionmanager2.patch

--- a/vendor/magento/framework/Session/SessionManager.php
+++ b/vendor/magento/framework/Session/SessionManager.php
@@ -185,7 +185,19 @@
             $this->registerSaveHandler();

-            // potential custom logic for session id (ex. switching between hosts)
-            $this->setSessionId($this->sidResolver->getSid($this));
-            session_start();
-            $this->validator->validate($this);
+            if (isset($_SESSION['new_session_id'])) {
+             // Not fully expired yet. Could be lost cookie by unstable network.
+             session_commit();
+             session_id($_SESSION['new_session_id']);
+             }
+            // potential custom logic for session id (ex. switching between hosts)
+            $this->setSessionId($this->sidResolver->getSid($this));
+            session_start();
+            if (isset($_SESSION['destroyed'])) {
+               if ($_SESSION['destroyed'] < time()-300) {
+                   $this->destroy(['clear_storage' => true]);
+
+               }
+            }
+            $this->validator->validate($this);
+

             register_shutdown_function([$this, 'writeClose']);
\ No newline at end of file

Okay, the reason i created 2 file because when i put them in one file, it will cause an error patch fragment without header which i still dont know how to fix.
After create this 2 patch file, open command line in your magento root folder, which in mine case is "magento_project".
And type git apply patches/sessionmanager.patch and git apply patches/sessionmanager2.patch.
If you got error when running this command, and it say something about line 468 and line 185, it highly because of your SessionManager.php in your project is different than mine.
Please check again in patch file and change so it look like SessionManager.php in your project.
Oh, and dont forget to check the white space in patch file. Its really important. The white space of patch file is always = white space in SessionManager.php +1, accept for the line which have + and - at the beginning of the line.

That's it. Thanks for reading. Hope it help some one :)
Gonna update if i know something else

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.