Session module source code parsing

Since HTTP was originally an anonymous, stateless request/response protocol, the server processes the request from the client and then sends a response back to the client. In order to provide personalized services to users, modern Web applications often need to identify users in requests or share data between users’ multiple requests. Sessions provide a way to store and share information about users across multiple requests. Laravel handles various built-in Session backend drivers through the same readable API.

Session support drivers:

  • file– Saves the Session instorage/framework/sessionsIn the.
  • cookie– Session Stores the cookies in secure encryption.
  • database– Session Stores data in the relational database.
  • memcached / redis– Sessions are stored in one of the fast, cache-based storage systems.
  • array– Sessions is stored in a PHP array and will not be persisted.

In this article, we will take a detailed look at the implementation principle of the Session service in Laravel, what parts the Session service consists of and the role of each part, when it is registered with the service container, when the request is enabled for the Session, and how to extend the Session driver.

Registering the Session Service

As mentioned in many previous articles, services are registered with the service container through the service provider. Laravel executes the providers register method in the providers array of config/app.php to register the services required by the framework. So it is easy to imagine that the Session service is also registered in the service container at this stage.

'providers'=> [ /* * Laravel Framework Service Providers... */ ...... Illuminate\Session\SessionServiceProvider::class ...... ] .Copy the code

If in will do have SessionServiceProvider we look at its source, take a look at the session service registration details

namespace Illuminate\Session;

use Illuminate\Support\ServiceProvider;
use Illuminate\Session\Middleware\StartSession;

class SessionServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerSessionManager();

        $this->registerSessionDriver();

        $this->app->singleton(StartSession::class);
    }

    /**
     * Register the session manager instance.
     *
     * @return void
     */
    protected function registerSessionManager()
    {
        $this->app->singleton('session'.function ($app) {
            return new SessionManager($app);
        });
    }

    /**
     * Register the session driver instance.
     *
     * @return void
     */
    protected function registerSessionDriver()
    {
        $this->app->singleton('session.store'.function ($app) {
            // First, we will create the session manager which is responsible for the
            // creation of the various session drivers when they are needed by the
            // application instance, and will resolve them on a lazy load basis.
            return $app->make('session')->driver(); }); }}Copy the code

There are three services registered with SessionServiceProvider:

  • sessionThe session service resolves to oneSessionManagerObject, which creates the session drive and resolves the drive as needed (lazy loading), and proxies all method calls to access and update session data to the corresponding session drive.
  • session.storeSession driver,Illuminate\Session\StoreAn instance of theStore the classTo achieve theIlluminate\Contracts\Session\SessionContracts provide a unified interface for developers to access Session data through different driversSessionHandlerTo access thedatabase,redis,memcacheSession data in different storage media.
  • StartSession::classMiddleware, which provides the ability to open a Session at the start of a request, write the Session identifier into a Cookie before the response is sent to the client, and act as aThe terminate middlewareAfter the response is sent to the client it is interminate()Method to store updates to session data in the request to the storage medium.

Create Session Drive

SessionManager is used to create session drivers. It defines a variety of sample drive creators (methods for creating drive instances).

<? php namespace Illuminate\Session; use Illuminate\Support\Manager; Class SessionManager extends Manager {/** * calls the custom driver creator (registered with Session::extend) ** @param string$driver
     * @return mixed
     */
    protected function callCustomCreator($driver)
    {
        return $this->buildSession(parent::callCustomCreator($driver)); } /** * Create session drivers of array type (not persistent) ** @return \Illuminate\Session\Store
     */
    protected function createArrayDriver()
    {
        return $this->buildSession(new NullSessionHandler); } /** * create Cookie session drive ** @return \Illuminate\Session\Store
     */
    protected function createCookieDriver()
    {
        return $this->buildSession(new CookieSessionHandler(
            $this->app['cookie'].$this->app['config'] ['session.lifetime'])); } /** * create file session drive ** @return \Illuminate\Session\Store
     */
    protected function createFileDriver()
    {
        return $this->createNativeDriver(); } /** * create file session drive ** @return \Illuminate\Session\Store
     */
    protected function createNativeDriver()
    {
        $lifetime = $this->app['config'] ['session.lifetime'];

        return $this->buildSession(new FileSessionHandler(
            $this->app['files'].$this->app['config'] ['session.files'].$lifetime)); } /** * create a database-type session driver ** @return \Illuminate\Session\Store
     */
    protected function createDatabaseDriver()
    {
        $table = $this->app['config'] ['session.table'];

        $lifetime = $this->app['config'] ['session.lifetime'];

        return $this->buildSession(new DatabaseSessionHandler(
            $this->getDatabaseConnection(), $table.$lifetime.$this->app
        ));
    }

    /**
     * Get the database connection for the database driver.
     *
     * @return \Illuminate\Database\Connection
     */
    protected function getDatabaseConnection()
    {
        $connection = $this->app['config'] ['session.connection'];

        return $this->app['db']->connection($connection);
    }

    /**
     * Create an instance of the APC session driver.
     *
     * @return \Illuminate\Session\Store
     */
    protected function createApcDriver()
    {
        return $this->createCacheBased('apc'); } /** * create the memcache session drive ** @return \Illuminate\Session\Store
     */
    protected function createMemcachedDriver()
    {
        return $this->createCacheBased('memcached'); } /** * Create the Redis session drive ** @return \Illuminate\Session\Store
     */
    protected function createRedisDriver()
    {
        $handler = $this->createCacheHandler('redis');

        $handler->getCache()->getStore()->setConnection(
            $this->app['config'] ['session.connection']);return $this->buildSession($handler); } /** * Create cache-based session drive (this method is called when creating memcache, APC drive) ** @param string$driver
     * @return \Illuminate\Session\Store
     */
    protected function createCacheBased($driver)
    {
        return $this->buildSession($this->createCacheHandler($driver)); } /** * Create cache-based session handler ** @param string$driver
     * @return \Illuminate\Session\CacheBasedSessionHandler
     */
    protected function createCacheHandler($driver)
    {
        $store = $this->app['config']->get('session.store') ?: $driver;

        return new CacheBasedSessionHandler(
            clone $this->app['cache']->store($store),
            $this->app['config'] ['session.lifetime']); } /** * Build session driver ** @param \SessionHandlerInterface$handler
     * @return \Illuminate\Session\Store
     */
    protected function buildSession($handler)
    {
        if ($this->app['config'] ['session.encrypt']) {
            return $this->buildEncryptedSession($handler);
        }

        return new Store($this->app['config'] ['session.cookie'].$handler); } /** * Build an encrypted Session driver ** @param \SessionHandlerInterface$handler
     * @return \Illuminate\Session\EncryptedStore
     */
    protected function buildEncryptedSession($handler)
    {
        return new EncryptedStore(
            $this->app['config'] ['session.cookie'].$handler.$this->app['encrypter']); } /** * get the configuration ** @ in config/session.phpreturn array
     */
    public function getSessionConfig()
    {
        return $this->app['config'] ['session']; } /** * get the session driver name ** @ in the configurationreturn string
     */
    public function getDefaultDriver()
    {
        return $this->app['config'] ['session.driver']; } /** * Set the session name in the configuration ** @param string$name
     * @return void
     */
    public function setDefaultDriver($name)
    {
        $this->app['config'] ['session.driver'] = $name; }}Copy the code

The SessionHandler is used to access the data in the storage medium. SessionHandler is used to access the data in the storage medium. Different sessionHandlers implement PHP’s built-in SessionHandlerInterface, so drivers can access data in different session storage media through the same interface method.

The drive accesses Session data

$request-> Session () : $request-> Session () : $request-> Session () In the Illuminate Session\Store source code, you can also see that the Session methods used in Laravel are defined here.

Session::get($key);
Session::has($key);
Session::put($key.$value);
Session::pull($key);
Session::flash($key.$value);
Session::forget($key);
Copy the code

Each of these session methods can be concretely implemented in the Illuminate session Store class

<? php namespace Illuminate\Session; use Closure; use Illuminate\Support\Arr; use Illuminate\Support\Str; use SessionHandlerInterface; use Illuminate\Contracts\Session\Session; class Store implements Session { /** * The session ID. * * @var string */ protected$id;

    /**
     * The session name.
     *
     * @var string
     */
    protected $name;

    /**
     * The session attributes.
     *
     * @var array
     */
    protected $attributes = [];

    /**
     * The session handler implementation.
     *
     * @var \SessionHandlerInterface
     */
    protected $handler;

    /**
     * Session store started status.
     *
     * @var bool
     */
    protected $started = false;

    /**
     * Create a new session instance.
     *
     * @param  string $name
     * @param  \SessionHandlerInterface $handler
     * @param  string|null $id
     * @return void
     */
    public function __construct($name, SessionHandlerInterface $handler.$id = null)
    {
        $this->setId($id);
        $this->name = $name;
        $this->handler = $handler; } /** * The session handler reads data from the storage media temporarily stored in attributes ** @return bool
     */
    public function start()
    {
        $this->loadSession();

        if (! $this->has('_token')) {
            $this->regenerateToken();
        }

        return $this->started = true; } /** * Load session data from storage via the Session handler to the Attributes attribute ** @return void
     */
    protected function loadSession()
    {
        $this->attributes = array_merge($this->attributes, $this->readFromHandler()); } /** * reads session data ** @ from storage via handlerreturn array
     */
    protected function readFromHandler()
    {
        if ($data = $this->handler->read($this->getId())) {
            $data = @unserialize($this->prepareForUnserialize($data));

            if ($data! = =false&&! is_null($data) && is_array($data)) {
                return $data; }}return [];
    }

    /**
     * Prepare the raw string data from the session for unserialization.
     *
     * @param  string  $data
     * @return string
     */
    protected function prepareForUnserialize($data)
    {
        return $data; } /** * Saves session data to storage ** @return bool
     */
    public function save()
    {
        $this->ageFlashData();

        $this->handler->write($this->getId(), $this->prepareForStorage(
            serialize($this->attributes)
        ));

        $this->started = false;
    }

    /**
     * Checks if a key is present and not null.
     *
     * @param  string|array  $key
     * @return bool
     */
    public function has($key)
    {
        return ! collect(is_array($key)?$key : func_get_args())->contains(function ($key) {
            return is_null($this->get($key));
        });
    }

    /**
     * Get an item from the session.
     *
     * @param  string  $key
     * @param  mixed  $default
     * @return mixed
     */
    public function get($key.$default = null)
    {
        return Arr::get($this->attributes, $key.$default);
    }

    /**
     * Get the value of a given key and then forget it.
     *
     * @param  string  $key
     * @param  string  $default
     * @return mixed
     */
    public function pull($key.$default = null)
    {
        return Arr::pull($this->attributes, $key.$default);
    }

    /**
     * Put a key / value pair or array of key / value pairs in the session.
     *
     * @param  string|array  $key
     * @param  mixed       $value
     * @return void
     */
    public function put($key.$value = null)
    {
        if (! is_array($key)) {
            $key = [$key= >$value];
        }

        foreach ($key as $arrayKey= >$arrayValue) {
            Arr::set($this->attributes, $arrayKey.$arrayValue);
        }
    }

    /**
     * Flash a key / value pair to the session.
     *
     * @param  string  $key
     * @param  mixed   $value
     * @return void
     */
    public function flash(string $key.$value = true)
    {
        $this->put($key.$value);

        $this->push('_flash.new'.$key);

        $this->removeFromOldFlashData([$key]);
    }

    /**
     * Remove one or many items from the session.
     *
     * @param  string|array  $keys
     * @return void
     */
    public function forget($keys)
    {
        Arr::forget($this->attributes, $keys);
    }

    /**
     * Remove all of the items from the session.
     *
     * @return void
     */
    public function flush()
    {
        $this->attributes = [];
    }


    /**
     * Determine if the session has been started.
     *
     * @return bool
     */
    public function isStarted()
    {
        return $this->started;
    }

    /**
     * Get the name of the session.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set the name of the session.
     *
     * @param  string  $name
     * @return void
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Get the current session ID.
     *
     * @return string
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set the session ID.
     *
     * @param  string  $id
     * @return void
     */
    public function setId($id)
    {
        $this->id = $this->isValidId($id)?$id : $this->generateSessionId();
    }

    /**
     * Determine if this is a valid session ID.
     *
     * @param  string  $id
     * @return bool
     */
    public function isValidId($id)
    {
        return is_string($id) && ctype_alnum($id) && strlen($id) = = = 40; } /** * Get a new, random session ID. * * @return string
     */
    protected function generateSessionId()
    {
        return Str::random(40);
    }

    /**
     * Set the existence of the session on the handler if applicable.
     *
     * @param  bool  $value
     * @return void
     */
    public function setExists($value)
    {
        if ($this->handler instanceof ExistenceAwareInterface) {
            $this->handler->setExists($value);
        }
    }

    /**
     * Get the CSRF token value.
     *
     * @return string
     */
    public function token()
    {
        return $this->get('_token');
    }
    
    /**
     * Regenerate the CSRF token value.
     *
     * @return void
     */
    public function regenerateToken()
    {
        $this->put('_token', Str::random(40)); }}Copy the code

Because there’s a lot of source code for drives, I’ve left a few common uses and methods and annotated the key ones. For the full source code, see the Illuminate Session Store class. From the source of the Store class we can find:

  • There’s one in every session data_tokenData to doCSRFProtect against it.
  • When a Session is enabled, Session data is read from the store and stored temporarily to attributes.
  • The driver provides the application with methods to manipulate session data directly from the Attributes attribute.

At the same time, there will be some questions. In normal development, we did not take the initiative to open and save sessions. How did data load and persist? Sharing data between user requests through session requires storing a session ID in the client cookie. Where is this cookie set?

The solution to the above two problems is the third service we started with, StartSession middleware

StartSession middleware

<? php namespace Illuminate\Session\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Illuminate\Session\SessionManager; use Illuminate\Contracts\Session\Session; use Illuminate\Session\CookieSessionHandler; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Response; class StartSession { /** * The session manager. * * @var \Illuminate\Session\SessionManager */ protected$manager;

    /**
     * Indicates if the session was handled for the current request.
     *
     * @var bool
     */
    protected $sessionHandled = false;

    /**
     * Create a new session middleware.
     *
     * @param  \Illuminate\Session\SessionManager  $manager
     * @return void
     */
    public function __construct(SessionManager $manager)
    {
        $this->manager = $manager;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $this->sessionHandled = true;

        // If a session driver has been configured, we will need to start the session here
        // so that the data is ready for an application. Note that the Laravel sessions
        // do not make use of PHP "native" sessions in any way since they are crappy.
        if ($this->sessionConfigured()) {
            $request->setLaravelSession(
                $session = $this->startSession($request));$this->collectGarbage($session);
        }

        $response = $next($request);

        // Again, ifthe session has been configured we will need to close out the session // so that the attributes may be persisted to some  storage medium. We will also // add the session identifier cookie to the application response headers now.if ($this->sessionConfigured()) {
            $this->storeCurrentUrl($request.$session);

            $this->addCookieToResponse($response.$session);
        }

        return $response;
    }

    /**
     * Perform any final actions for the request lifecycle.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Symfony\Component\HttpFoundation\Response  $response
     * @return void
     */
    public function terminate($request.$response)
    {
        if ($this->sessionHandled && $this->sessionConfigured() && ! $this->usingCookieSessions()) {
            $this->manager->driver()->save();
        }
    }

    /**
     * Start the session for the given request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Contracts\Session\Session
     */
    protected function startSession(Request $request)
    {
        return tap($this->getSession($request), function ($session) use ($request) {
            $session->setRequestOnHandler($request);

            $session->start();
        });
    }

    /**
     * Add the session cookie to the application response.
     *
     * @param  \Symfony\Component\HttpFoundation\Response  $response
     * @param  \Illuminate\Contracts\Session\Session  $session
     * @return void
     */
    protected function addCookieToResponse(Response $response, Session $session)
    {
        if ($this->usingCookieSessions()) {// Save session data to a cookie whose name is the ID identifier of this session data$this->manager->driver()->save();
        }

        if ($this->sessionIsPersistent($config = $this->manager->getSessionConfig()) {// Save the session ID identifier to the cookie. The cookie name is set in the session configuration file$response->headers->setCookie(new Cookie(
                $session->getName(), $session->getId(), $this->getCookieExpirationDate(),
                $config['path'].$config['domain'].$config['secure']????false.$config['http_only']????true.false.$config['same_site']???? null )); } } /** * Determineif the configured session driver is persistent.
     *
     * @param  array|null  $config
     * @return bool
     */
    protected function sessionIsPersistent(array $config = null)
    {
        $config = $config? :$this->manager->getSessionConfig();

        return ! in_array($config['driver'], [null, 'array']);
    }

    /**
     * Determine if the session is using cookie sessions.
     *
     * @return bool
     */
    protected function usingCookieSessions()
    {
        if ($this->sessionConfigured()) {
            return $this->manager->driver()->getHandler() instanceof CookieSessionHandler;
        }

        return false; }}Copy the code

Similarly, I only reserved the most critical code. It can be seen that the middleware performs session start when the request comes in, and then sets the session ID in the cookie response header before the response is returned to the client. The cookie name is set by the cookie configuration item in config/session.php and the value is the ID identifier of this session. And at the same time if the session driver is using a Cookie session handler it’s also going to store the session data in a cookie whose name is the session ID identifier for this particular session (well, it’s a little convoluted, The session data stored in Redis is stored in the cookie with the ID as the cookie name. The value is JSON formatted session data.

$this-> Manager ->driver()->save(); $this-> Manager -> Driver ()->save(); Persisting session data to storage (I’m not sure why I’m not persisting it here, maybe after reading the Cookie service source).

Add a custom driver

MongoHandler must implement a unified SessionHandlerInterface method:

<? php namespace App\Extensions; class MongoHandler implements SessionHandlerInterface { publicfunction open($savePath.$sessionName) {}
    public function close() {}
    public function read($sessionId) {}
    public function write($sessionId.$data) {}
    public function destroy($sessionId) {}
    public function gc($lifetime) {}}Copy the code

After defining the driver, register it with AppServiceProvider:

<? php namespace App\Providers; use App\Extensions\MongoSessionStore; use Illuminate\Support\Facades\Session; use Illuminate\Support\ServiceProvider; Class SessionServiceProvider extends ServiceProvider {/** * Performs a post-registration boot service. * * @return void
     */
    public function boot()
    {
        Session::extend('mongo'.function ($app) {
            // Return implementation of SessionHandlerInterface...
            returnnew MongoSessionStore; }); }}Copy the code

This method will be used to create a Mongo Session driver using the Driver method of SessionManager by calling callCustomCreator.

This article has been included in a series of articles Laravel source learning, welcome to visit to read.