聽說過 Laravel 近幾年很火,之前也接觸過 YII 2,CodeIgniter 3,Symfony 等 php 框架,一直沒有機會學習 Laravel。現在公司計劃轉向 Laravel 框架,廢話少說,趕緊學起來。
Laravel 框架,當然也是一個基於 composer 工具管理依賴包的開源項目。初看 Laravel 框架代碼就可以發現,整體架構耦合度小,開發比較靈活。在 Laravel 框架下,一次請求的生命週期可以用下面的流程圖來概括。
下面,主要從以下幾個方面分析框架:
一、Laravel 框架生命週期流程圖分析
二、Laravel 框架底層概念理解
(1)服務容器
(2)服務提供者
(3)門面
(4)契約
(5)管道
(6)事件
(7)廣播
一、Laravel 框架生命週期流程圖分析(見上圖)
圖中比較詳細地畫出了一次請求下整個框架的生命週期,下面主要從幾個重要的生命時期進行分析。
(1)構造應用程序框架,即服務容器
在構造應用程序框架過程中,主要完成以下方面工作:
(a)設置基本路徑,即綁定所有應用程序路徑到容器中,包括:
/**
* Bind all of the application paths in the container.
*
* @return void
*/
protected function bindPathsInContainer()
{
$this->instance('path', $this->path()); //app目錄
$this->instance('path.base', $this->basePath()); //根目錄
$this->instance('path.lang', $this->langPath()); //語言包目錄
$this->instance('path.config', $this->configPath()); //配置文件目錄
$this->instance('path.public', $this->publicPath()); //對外公開目錄
$this->instance('path.storage', $this->storagePath()); //文件存儲目錄
$this->instance('path.database', $this->databasePath()); //數據庫配置目錄
$this->instance('path.resources', $this->resourcePath()); //前端資源目錄
$this->instance('path.bootstrap', $this->bootstrapPath()); //啓動目錄
}
(b)將基本綁定註冊到容器中,包括:
/**
* Register the basic bindings into the container.
*
* @return void
*/
protected function registerBaseBindings()
{
static::setInstance($this); //將自身設置爲服務容器
$this->instance('app', $this); //綁定應用對象,別名爲 'app'
$this->instance(Container::class, $this); //綁定應用對象,別名爲 Container::class
$this->singleton(Mix::class); //註冊一個 Mix 單例類,別名爲 Mix::class
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
)); //綁定應用對象,別名爲 PackageManifest::class
}
(c)註冊所有基本服務提供者(事件、日誌和路由),包括:
/**
* Register all of the base service providers.
*
* @return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
<?php
namespace Illuminate\Events;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;
use Illuminate\Support\ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
* 註冊一個任務分發器的單例類,別名爲 'events',用於處理容器中所有的隊列任務
*
* @return void
*/
public function register()
{
$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make(QueueFactoryContract::class);
});
});
}
}
<?php
namespace Illuminate\Log;
use Illuminate\Support\ServiceProvider;
class LogServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
* 註冊一個記錄日誌的單例類,別名爲 'log'
*
* @return void
*/
public function register()
{
$this->app->singleton('log', function ($app) {
return new LogManager($app);
});
}
}
<?php
namespace Illuminate\Routing;
use Exception;
use Illuminate\Contracts\Routing\ResponseFactory as ResponseFactoryContract;
use Illuminate\Contracts\Routing\UrlGenerator as UrlGeneratorContract;
use Illuminate\Contracts\View\Factory as ViewFactoryContract;
use Illuminate\Routing\Contracts\ControllerDispatcher as ControllerDispatcherContract;
use Illuminate\Support\ServiceProvider;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7\Response as PsrResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
class RoutingServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
* 註冊多個類,具體見下面註釋
*
* @return void
*/
public function register()
{
$this->registerRouter(); //註冊路由器單例類,別名爲 'router'
$this->registerUrlGenerator(); //註冊路由產生器單例類,別名爲 'url'
$this->registerRedirector(); //註冊重定向單例類,別名爲 'redirect'
$this->registerPsrRequest(); //註冊一個PSR-7請求類,別名爲 ServerRequestInterface::class
$this->registerPsrResponse(); //註冊一個PSR-7響應類,別名爲 ResponseInterface::class
$this->registerResponseFactory(); //註冊響應工廠單例類,別名爲 ResponseFactoryContract::class,用於處理重定向對象 redirect
$this->registerControllerDispatcher(); //註冊控制器分發單例類,別名爲 ControllerDispatcherContract::class
}
...
}
(d)在容器中註冊核心類別名,包括:
/**
* Register the core class aliases in the container.
*
* @return void
*/
public function registerCoreContainerAliases()
{
foreach ([
'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class, \Psr\SimpleCache\CacheInterface::class],
'cache.psr6' => [\Symfony\Component\Cache\Adapter\Psr16Adapter::class, \Symfony\Component\Cache\Adapter\AdapterInterface::class, \Psr\Cache\CacheItemPoolInterface::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
'db' => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Hashing\HashManager::class],
'hash.driver' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
'mail.manager' => [\Illuminate\Mail\MailManager::class, \Illuminate\Contracts\Mail\Factory::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'redis.connection' => [\Illuminate\Redis\Connections\Connection::class, \Illuminate\Contracts\Redis\Connection::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
(2)處理HTTP請求 $response = (App\Http\Kernel) $kernel->handle($request);
(A)依次啓動 App\Http\Kernel :: $bootstrappers 中定義的類,見代碼:
/**
* The bootstrap classes for the application.
*
* @var array
*/
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
(a)加載環境變量類 LoadEnvironmentVariables::bootstrap();
<?php
namespace Illuminate\Foundation\Bootstrap;
use Dotenv\Dotenv;
use Dotenv\Exception\InvalidFileException;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Env;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
class LoadEnvironmentVariables
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
if ($app->configurationIsCached()) {
return;
}
$this->checkForSpecificEnvironmentFile($app);
try {
$this->createDotenv($app)->safeLoad();
} catch (InvalidFileException $e) {
$this->writeErrorAndDie($e);
}
}
...
}
(b)加載配置類 LoadConfiguration::bootstrap();
<?php
namespace Illuminate\Foundation\Bootstrap;
use Exception;
use Illuminate\Config\Repository;
use Illuminate\Contracts\Config\Repository as RepositoryContract;
use Illuminate\Contracts\Foundation\Application;
use SplFileInfo;
use Symfony\Component\Finder\Finder;
class LoadConfiguration
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
$items = [];
// First we will see if we have a cache configuration file. If we do, we'll load
// the configuration items from that file so that it is very quick. Otherwise
// we will need to spin through every configuration file and load them all.
if (file_exists($cached = $app->getCachedConfigPath())) {
$items = require $cached;
$loadedFromCache = true;
}
// Next we will spin through all of the configuration files in the configuration
// directory and load each one into the repository. This will make all of the
// options available to the developer for use in various parts of this app.
$app->instance('config', $config = new Repository($items));
if (! isset($loadedFromCache)) {
$this->loadConfigurationFiles($app, $config);
}
// Finally, we will set the application's environment based on the configuration
// values that were loaded. We will pass a callback which will be used to get
// the environment in a web context where an "--env" switch is not present.
$app->detectEnvironment(function () use ($config) {
return $config->get('app.env', 'production');
});
date_default_timezone_set($config->get('app.timezone', 'UTC'));
mb_internal_encoding('UTF-8');
}
...
}
(c)處理異常類 HandleExceptions::bootstrap();
<?php
namespace Illuminate\Foundation\Bootstrap;
use ErrorException;
use Exception;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Contracts\Foundation\Application;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Throwable;
class HandleExceptions
{
/**
* Reserved memory so that errors can be displayed properly on memory exhaustion.
*
* @var string
*/
public static $reservedMemory;
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
self::$reservedMemory = str_repeat('x', 10240);
$this->app = $app;
error_reporting(-1);
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
register_shutdown_function([$this, 'handleShutdown']);
if (! $app->environment('testing')) {
ini_set('display_errors', 'Off');
}
}
...
}
(d)註冊外觀服務類 RegisterFacades::bootstrap();
<?php
namespace Illuminate\Foundation\Bootstrap;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Foundation\PackageManifest;
use Illuminate\Support\Facades\Facade;
class RegisterFacades
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
Facade::clearResolvedInstances();
Facade::setFacadeApplication($app);
AliasLoader::getInstance(array_merge(
$app->make('config')->get('app.aliases', []),
$app->make(PackageManifest::class)->aliases()
))->register();
}
}
(e)註冊服務提供者類 RegisterProviders::bootstrap();
註冊 config/app.php 下配置項 providers 中的所有服務提供者
<?php
namespace Illuminate\Foundation\Bootstrap;
use Illuminate\Contracts\Foundation\Application;
class RegisterProviders
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
$app->registerConfiguredProviders();
}
}
(f)啓動服務提供者類 BootProviders::bootstrap();
啓動應用程序 $app->boot();
<?php
namespace Illuminate\Foundation;
use Closure;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;
use Illuminate\Contracts\Foundation\CachesConfiguration;
use Illuminate\Contracts\Foundation\CachesRoutes;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface
{
...
/**
* Boot the application's service providers.
*
* @return void
*/
public function boot()
{
if ($this->isBooted()) {
return;
}
// Once the application has booted we will also fire some "booted" callbacks
// for any listeners that need to do work after this initial booting gets
// finished. This is useful when ordering the boot-up processes we run.
$this->fireAppCallbacks($this->bootingCallbacks);
array_walk($this->serviceProviders, function ($p) {
$this->bootProvider($p);
});
$this->booted = true;
$this->fireAppCallbacks($this->bootedCallbacks);
}
...
}
(B)通過管道處理 Http 中間件,然後繼續分發路由
<?php
namespace Illuminate\Foundation\Http;
use Exception;
use Illuminate\Contracts\Http\Kernel as KernelContract;
use Illuminate\Foundation\Http\Events\RequestHandled;
use Illuminate\Routing\Pipeline;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Facade;
use InvalidArgumentException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Throwable;
class Kernel implements KernelContract
{
...
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
...
}
在處理中間件過程中,先處理全局中間件,再處理分組中間件(如api,web,...),最後處理路由中間件,最後請求被指派到某個控制器中。
二、Laravel 框架底層概念理解
未完,待續...
在一個服務提供者中, 可以通過 $this->app 變量訪問容器;
在任何地方都可以調用 function app($abstract = null, array $parameters = []) 輔助函數獲取服務容器及其對象;
也可以使用 \Illuminate\Container\Container::getInstance(); 獲取服務容器,使用 Container::getInstance()->make($abstract, $parameters); 解析服務容器對象。