Maison >cadre php >Laravel >Contenu de base du framework Laravel : analyse détaillée du code source de la session

Contenu de base du framework Laravel : analyse détaillée du code source de la session

不言
不言original
2018-08-14 11:16:162315parcourir

Le contenu de cet article concerne le contenu principal du framework Laravel : une analyse détaillée du code source de la session. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Analyse du code source du module de session

Étant donné que HTTP était à l'origine un protocole de requête/réponse anonyme et sans état, le serveur traite la requête du client puis renvoie une réponse au client. Afin de fournir aux utilisateurs des services personnalisés, les applications Web modernes doivent souvent identifier l'utilisateur dans la requête ou partager des données entre les multiples requêtes de l'utilisateur. Les sessions permettent de stocker et de partager des informations sur un utilisateur entre plusieurs requêtes. Laravel Gérez divers pilotes d'arrière-plan de session intégrés via la même API lisible.

Pilotes pris en charge par la session :

  • file - Enregistrer la session dans storage/framework/sessions.

  • cookie - La session est enregistrée dans un cookie sécurisé et crypté.

  • database - La session est stockée dans une base de données relationnelle.

  • memcached / redis - Les sessions sont enregistrées dans l'un des systèmes de stockage rapides et basés sur le cache.

  • array - Les sessions sont enregistrées dans des tableaux PHP et ne seront pas conservées.

Dans cet article, examinons en détail le principe de mise en œuvre du service Laravel dans Session, de quelles parties se compose le service Session, le rôle de chaque partie, et de quoi s'agit-il ? Quand la demande a été enregistrée dans le conteneur de service, quand la session a été activée et comment étendre le pilote pour la session.

Enregistrer le service de session

Comme mentionné dans de nombreux articles précédents, le service est enregistré dans le conteneur de services via le fournisseur de services qui s'exécutera config/app.php en séquence pendant la phase de démarrage. La méthode Provider providers dans le tableau register est utilisée pour enregistrer les services requis par le framework, nous pouvons donc facilement penser que le service de session est également enregistré dans le conteneur de services à ce stade.

'providers' => [

    /*
     * Laravel Framework Service Providers...
     */
    ......
    Illuminate\Session\SessionServiceProvider::class
    ......
],

Effectivement, il y a bien providers dans SessionServiceProvider Jetons un coup d'œil à son code source et voyons les détails d'enregistrement du service de session

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();
        });
    }
}

. Un total d'enregistrements dans SessionServiceProvider Il existe trois services :

  • session service Une fois le service de session analysé, c'est un SessionManager objet. créez le pilote de session et analysez le pilote si nécessaire (retard de chargement). De plus, tous les appels de méthode pour accéder et mettre à jour les données de session sont implémentés par proxy vers le pilote de session correspondant.

  • session.store Pilote de session, une instance de IlluminateSessionStore, Store类 implémente le contrat IlluminateContractsSessionSession pour fournir aux développeurs une interface unifiée pour accéder aux données de session. SessionHandler pour accéder aux données de session dans différents supports de stockage tels que database, redis, memcache, etc.

  • StartSession::class middleware, qui ouvre la session au début de la requête et écrit l'identifiant de session dans le cookie avant que la réponse ne soit envoyée au client. De plus, en tant que <.> Une fois la réponse envoyée au client, celui-ci enregistrera les mises à jour des données de session dans la requête sur le support de stockage dans la méthode terminate中间件. terminate()

Créer un pilote de session

Comme mentionné ci-dessus,

est utilisé pour créer un pilote de session, qui définit différents créateurs de pilotes (méthode de création d'instance de pilote) Regardez sa source code pour voir que le pilote de session est créé : SessionManager

<?php

namespace Illuminate\Session;

use Illuminate\Support\Manager;

class SessionManager extends Manager
{
    /**
     * 调用自定义驱动创建器 (通过Session::extend注册的)
     *
     * @param  string  $driver
     * @return mixed
     */
    protected function callCustomCreator($driver)
    {
        return $this->buildSession(parent::callCustomCreator($driver));
    }

    /**
     * 创建数组类型的session驱动器(不会持久化)
     *
     * @return \Illuminate\Session\Store
     */
    protected function createArrayDriver()
    {
        return $this->buildSession(new NullSessionHandler);
    }

    /**
     * 创建Cookie session驱动器
     *
     * @return \Illuminate\Session\Store
     */
    protected function createCookieDriver()
    {
        return $this->buildSession(new CookieSessionHandler(
            $this->app['cookie'], $this->app['config']['session.lifetime']
        ));
    }

    /**
     * 创建文件session驱动器
     *
     * @return \Illuminate\Session\Store
     */
    protected function createFileDriver()
    {
        return $this->createNativeDriver();
    }

    /**
     * 创建文件session驱动器
     *
     * @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
        ));
    }

    /**
     * 创建Database型的session驱动器
     *
     * @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');
    }

    /**
     * 创建memcache session驱动器
     *
     * @return \Illuminate\Session\Store
     */
    protected function createMemcachedDriver()
    {
        return $this->createCacheBased('memcached');
    }

    /**
     * 创建redis session驱动器
     *
     * @return \Illuminate\Session\Store
     */
    protected function createRedisDriver()
    {
        $handler = $this->createCacheHandler('redis');

        $handler->getCache()->getStore()->setConnection(
            $this->app['config']['session.connection']
        );

        return $this->buildSession($handler);
    }

    /**
     * 创建基于Cache的session驱动器 (创建memcache、apc驱动器时都会调用这个方法)
     *
     * @param  string  $driver
     * @return \Illuminate\Session\Store
     */
    protected function createCacheBased($driver)
    {
        return $this->buildSession($this->createCacheHandler($driver));
    }

    /**
     * 创建基于Cache的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']
        );
    }

    /**
     * 构建session驱动器
     *
     * @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);
    }

    /**
     * 构建加密的Session驱动器
     *
     * @param  \SessionHandlerInterface  $handler
     * @return \Illuminate\Session\EncryptedStore
     */
    protected function buildEncryptedSession($handler)
    {
        return new EncryptedStore(
            $this->app['config']['session.cookie'], $handler, $this->app['encrypter']
        );
    }

    /**
     * 获取config/session.php里的配置
     *
     * @return array
     */
    public function getSessionConfig()
    {
        return $this->app['config']['session'];
    }

    /**
     * 获取配置里的session驱动器名称
     *
     * @return string
     */
    public function getDefaultDriver()
    {
        return $this->app['config']['session.driver'];
    }

    /**
     * 设置配置里的session名称
     *
     * @param  string  $name
     * @return void
     */
    public function setDefaultDriver($name)
    {
        $this->app['config']['session.driver'] = $name;
    }
}
Grâce au code source de

, vous pouvez voir que le pilote fournit une interface d'accès unifiée au monde extérieur, et différente La raison pour laquelle différent Les types de lecteurs peuvent accéder à différents supports de stockage est que le lecteur accède aux données dans le support de stockage via SessionManager, et différents SessionHandler implémentent uniformément l'SessionHandler interface PHP intégrée, de sorte que les données du lecteur dans différentes sessions les supports de stockage sont accessibles via une méthode d'interface unifiée. SessionHandlerInterface

Le pilote accède aux données de session

Les développeurs utilisant la façade

ou Session pour accéder aux données de session sont transmis à la méthode du pilote correspondante via le service $request->session(), c'est-à-dire le session objet. Dans le code source de SessionManager on peut également voir que les méthodes de session utilisées dans IlluminateSessionStore sont définies ici. Laravel

Session::get($key);
Session::has($key);
Session::put($key, $value);
Session::pull($key);
Session::flash($key, $value);
Session::forget($key);
Les méthodes de session ci-dessus peuvent être implémentées de manière spécifique dans la classe

IlluminateSessionStore

<?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;
    }

    /**
     * 开启session, 通过session handler从存储介质中读出数据暂存在attributes属性里
     *
     * @return bool
     */
    public function start()
    {
        $this->loadSession();

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

        return $this->started = true;
    }

    /**
     * 通过session handler从存储中加载session数据暂存到attributes属性里
     *
     * @return void
     */
    protected function loadSession()
    {
        $this->attributes = array_merge($this->attributes, $this->readFromHandler());
    }

    /**
     * 通过handler从存储中读出session数据
     *
     * @return 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;
    }

    /**
     * 将session数据保存到存储中
     *
     * @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));
    }
}
Étant donné que le pilote a beaucoup de code source, je n'en laisse que quelques-uns couramment utilisés méthodes. et annoté les méthodes clés Pour le code source complet, vous pouvez afficher le code source de la classe

. Grâce au code source de la classe IlluminateSessionStore on peut trouver : Store

  • 每个session数据里都会有一个_token数据来做CSRF防范。

  • Session开启后会将session数据从存储中读出暂存到attributes属性。

  • 驱动器提供给应用操作session数据的方法都是直接操作的attributes属性里的数据。

同时也会产生一些疑问,在平时开发时我们并没有主动的去开启和保存session,数据是怎么加载和持久化的?通过session在用户的请求间共享数据是需要在客户端cookie存储一个session id的,这个cookie又是在哪里设置的?

上面的两个问题给出的解决方案是最开始说的第三个服务StartSession中间件

StartSession 中间件

<?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, if the 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()) {
            //将session数据保存到cookie中,cookie名是本条session数据的ID标识符
            $this->manager->driver()->save();
        }

        if ($this->sessionIsPersistent($config = $this->manager->getSessionConfig())) {
           //将本条session的ID标识符保存到cookie中,cookie名是session配置文件里设置的cookie名
            $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
            ));
        }
    }


    /**
     * Determine if 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;
    }
}

同样的我只保留了最关键的代码,可以看到中间件在请求进来时会先进行session start操作,然后在响应返回给客户端前将session id 设置到了cookie响应头里面, cookie的名称是由config/session.php里的cookie配置项设置的,值是本条session的ID标识符。与此同时如果session驱动器用的是CookieSessionHandler还会将session数据保存到cookie里cookie的名字是本条session的ID标示符(呃, 有点绕,其实就是把存在redis里的那些session数据以ID为cookie名存到cookie里了, 值是JSON格式化的session数据)。

最后在响应发送完后,在terminate方法里会判断驱动器用的如果不是CookieSessionHandler,那么就调用一次$this->manager->driver()->save();将session数据持久化到存储中 (我现在还没有搞清楚为什么不统一在这里进行持久化,可能看完Cookie服务的源码就清楚了)。

添加自定义驱动

关于添加自定义驱动,官方文档给出了一个例子,MongoHandler必须实现统一的SessionHandlerInterface接口里的方法:

<?php

namespace App\Extensions;

class MongoHandler implements SessionHandlerInterface
{
    public function open($savePath, $sessionName) {}
    public function close() {}
    public function read($sessionId) {}
    public function write($sessionId, $data) {}
    public function destroy($sessionId) {}
    public function gc($lifetime) {}
}

定义完驱动后在AppServiceProvider里注册一下:

<?php

namespace App\Providers;

use App\Extensions\MongoSessionStore;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;

class SessionServiceProvider extends ServiceProvider
{
    /**
     * 执行注册后引导服务。
     *
     * @return void
     */
    public function boot()
    {
        Session::extend('mongo', function ($app) {
            // Return implementation of SessionHandlerInterface...
            return new MongoSessionStore;
        });
    }
}

这样在用SessionManagerdriver方法创建mongo类型的驱动器的时候就会调用callCustomCreator方法去创建mongo类型的Session驱动器了。

相关推荐:

如何使用Larave制定一个MySQL数据库备份计划任务

Laravel框架下的配置管理系统的设计过程(附代码)

Laravel中collection类的使用方法总结(代码)

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn