听说过 Laravel 近几年很火,之前也接触过 YII 2,CodeIgniter 3,Symfony 等 php 框架,一直没有机会学习 Laravel。现在公司计划转向 Laravel 框架,废话少说,赶紧学起来。
Laravel 框架,当然也是一个基于 composer 工具管理依赖包的开源项目。初看 Laravel 框架代码就可以发现,整体架构耦合度小,开发比较灵活。
在 Laravel 框架下,一次请求的生命周期可以用下面的流程图来概括。
下面,主要从以下几个方面分析框架:
一、Laravel 框架生命周期流程图分析
二、Laravel 框架底层概念理解
(1)服务容器
(2)服务提供者
(3)门面
(4)契约
(5)管道
(6)事件
(7)广播
一、Laravel 框架生命周期流程图分析(见上图)
图中比较详细地画出了一次请求下整个框架的生命周期,下面主要从几个重要的生命时期进行分析。
(1)构造应用程序框架,即服务容器 (Illuminate\Foundation\Application)
应用程序框架即Appllication对象,别名为 Illuminate\Container\Container::class
获取方法:
全局函数app()
$this->app (ServiceProvider类, AuthManager等Manager类, Kernel类, TestCase类)
在构造应用程序框架过程中,主要完成以下方面工作:
(a)设置基本路径,即绑定所有应用程序路径到容器中,包括:
/**
* Bind all of the application paths in the container.
*
* @return void
*/
protected function bindPathsInContainer()
{
$this->instance('path', $this->path()); //app_path()
$this->instance('path.base', $this->basePath()); //base_path()
$this->instance('path.lang', $this->langPath()); //app('path.config')
$this->instance('path.config', $this->configPath()); //config_path()
$this->instance('path.public', $this->publicPath()); //public_path()
$this->instance('path.storage', $this->storagePath()); //storage_path()
$this->instance('path.database', $this->databasePath()); //database_path()
$this->instance('path.resources', $this->resourcePath()); //resource_path()
$this->instance('path.bootstrap', $this->bootstrapPath()); //app('path.bootstrap')
}
(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); 解析服务容器对象。