《目录》
1.请求的生命周期
2.应用的架构
3.服务提供者
4.服务容器
5.Facades外立面(从这节起,看中文版的:https://phphub.org/topics/1783)
6.用户认证
1.请求的生命周期
所有的请求都会被服务器导向public/index.php文件,它是整个框架的入口点。首先载入Composer提供的类自动加载器,然后获取lavarel应用的一个实例(从bootstrap/app.php中)。 进来的所有请求,根据类型不同,要么送进HTTP kernel,要么就送进console kernel。kernel位于app/Http/Kernel.php中,它是Illuminate\Foundation\Http\Kernel类的子类,其中定义有bootstrappers数组,该操作在处理请求前首先被执行,对错误、日志等很多方面进行配置。其中,也定义了很多中间件,对session、CSRF验证、维护模式等进行处理。kernel中的handle方法可以看做是一个黑盒,输入为请求对象,输出为响应对象。 另一个重要的操作是载入service provider,它位于config/app.php的providers数组中,首先将执行所有provider的register函数,然后执行他们的boot函数。service provider可以提供database, queue, validation, 和routing方面的各种组件,它是lavar启动过程中最重要的一环。 这些都建立起来以后,请求将送至router,根据路由规则进行分发处理。 总而言之,service provider是laravel启动的关键,app实例被创建、service被注册、然后请求就交给app处理,如此简单!理解service provider是关键、它位于app/Providers中,默认地,可以在AppServiceProvider中添加自定义的启动代码、容器的绑定方式等;也可以创建单独的几个文件以负责更细粒度的provider。
2.应用的架构
lavarel并不会限制将类放在哪个目录下,只要是composer可以自动加载就可以。 目录结构如下: app保存应用的核心代码 bootstrap保存启动框架的代码和一些自动加载配置等,cache是为了优化启动速度文件夹。 config包含所有的配置文件 database包含数据库的migration和seeds public包含前端控制和静态文件 resources包含视图文件、本地化文件、和raw assets (LESS, SASS, CoffeeScript)等 storage包含编译好的blade模板、基于文件的session、缓存和其他一些由框架生成的文件。分为app文件夹(可以保存任何被框架使用的文件)、framework文件夹(保存生成的文件和缓存)、logs文件夹(保存所有的日志文件)。 tests文件夹包含测试代码 vendor文件夹包含composer依赖包 app文件夹详解(该文件夹中的许多类都可以通过artisan make命令生成): Console和Http文件夹相当于是提供了框架核心功能的api,http和cli是两种与框架交互的方式,但是并没有功能性的代码,他们只会对应用发起一些命令。console中包含了artisan命令,http中包含了中间件、控制器、请求等。 Events文件夹是event类所在地,它提供了灵活和解耦的方式,以通知应用的其他部分某事件已发生。 Exceptions包含了异常处理类。 Jobs是队列任务的所在地,可以执行队列任务,也可以与现有的请求生命周期同步执行。 Listeners文件夹中是事件的handler所在地,例如UserRegistered事件就有可能是被SendWelcomeEmail监听器所处理的。 Policies文件夹是保存认证策略的地方,它决定登录用户可以对哪些资源进行哪些操作。
3.服务提供者Service Provider
// config/app.php中的providers数组是所有provider的列表,但是它们中的许多都是延迟加载,意味着只有在请求体需要时,它才进行载入。 // 所有的provider都继承Illuminate\Support\ServiceProvider抽象类,唯一必须实现的方法是register,其中只能写将起绑定到service container的代码。由下列命令生成一个provider: php artisan make:provider RiakServiceProvider //下列是一个provider示例: php namespace App\Providers; use Riak\Connection; use Illuminate\Support\ServiceProvider; class RiakServiceProvider extends ServiceProvider { /** * Register bindings in the container. * * @return void */ public function register()//此处使用register方法在服务容器中定义了一个Riak\Connection类的实例,而且是“单例” { $this->app->singleton(Connection::class, function ($app) { return new Connection(config('riak')); }); } } //当我们想要注册一个view composer时,需要在boot方法中。boot方法的调用是在所有其他的provider都已经被注册后,所以在函数中可以使用框架提供的任何service。下面展示了boot的使用: php namespace App\Providers; use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { // Other Service Provider Properties... /** * Register any other events for your application. * * @param \Illuminate\Contracts\Events\Dispatcher $events * @return void */ public function boot(DispatcherContract $events) { parent::boot($events); view()->composer('view', function () { // }); } } //可以在boot方法中type-hint任何类,服务容器可以帮你完成依赖注入: use Illuminate\Contracts\Routing\ResponseFactory; public function boot(ResponseFactory $factory) { $factory->macro('caps', function ($value) { // }); } //注册自己的provider,位于config/app.php中providers数组: 'providers' => [ // Other Service Providers App\Providers\AppServiceProvider::class, ], //通过设置provider的defer属性为true,并且定义一个provides方法,可以避免该provider每次请求时,都从文件系统中加载,只有在实际需要时,才真正加载进来。Lavarel会保存这些延迟加载的provider,还有他们对应的类名,仅当实际需要时,就可以加载进来: php namespace App\Providers; use Riak\Connection; use Illuminate\Support\ServiceProvider; class RiakServiceProvider extends ServiceProvider { /** * Indicates if loading of the provider is deferred. * * @var bool */ protected $defer = true; /** * Register the service provider. * * @return void */ public function register() { $this->app->singleton(Connection::class, function ($app) { return new Connection($app['config']['riak']); }); } /** * Get the services provided by the provider. * * @return array */ public function provides()//该方法只用返回注册到服务容器中的服务绑定名即可 { return [Connection::class]; } }
4.服务容器Service Container
//首先,请参考https://phphub.org/topics/789以了解什么是服务容器、依赖注入等 //第一个示例: //因为每购买一个podcast,就会发送邮件,这里将mailer作为依赖注入 php namespace App\Jobs; use App\User; use Illuminate\Contracts\Mail\Mailer; use Illuminate\Contracts\Bus\SelfHandling; class PurchasePodcast implements SelfHandling { /** * The mailer implementation. */ protected $mailer; /** * Create a new instance. * * @param Mailer $mailer * @return void */ public function __construct(Mailer $mailer) { $this->mailer = $mailer; } /** * Purchase a podcast. * * @return void */ public function handle() { // } } //几乎所有的容器绑定都是在provider中的,在provider中,可以通过$this->app实例来访问container对象: $this->app->bind('HelpSpot\API', function ($app) { //这里接受了app作为参数,以便我们可以通过app对象获取子依赖 return new HelpSpot\API($app['HttpClient']); }); //单例模式 $this->app->singleton('FooBar', function ($app) { return new FooBar($app['SomethingElse']); }); //绑定一个具体对象 $fooBar = new FooBar(new SomethingElse); $this->app->instance('FooBar', $fooBar); //将接口绑定到一个特定实现 //第一个参数是接口,第二个参数是实现类 $this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher'); //在使用该接口的地方,就会自动注入该实现: use App\Contracts\EventPusher; /** * Create a new class instance. * * @param EventPusher $pusher * @return void */ public function __construct(EventPusher $pusher) { $this->pusher = $pusher; } //上下文绑定Contextual Binding $this->app->when('App\Handlers\Commands\CreateOrderHandler') ->needs('App\Contracts\EventPusher') ->give('App\Services\PubNubEventPusher'); //绑定一个特定整数 $this->app->when('App\Handlers\Commands\CreateOrderHandler') ->needs('$maxOrderCount') ->give(10); //通过标签管理 //当构建一个report集成器时,需要不同类型的report,此时将他们标注同一个标签,以实现一次性绑定全部 $this->app->bind('SpeedReport', function () { // }); $this->app->bind('MemoryReport', function () { // }); $this->app->tag(['SpeedReport', 'MemoryReport'], 'reports'); //使用时: $this->app->bind('ReportAggregator', function ($app) { return new ReportAggregator($app->tagged('reports')); }); //解析: $fooBar = $this->app->make('FooBar'); $fooBar = $this->app['FooBar']; //更常用的方式是在controller、listener、jobs、middleware等类的构造对象中进行type-hint,laravel可以自动解析依赖 //监听解析事件,使用resolving方法: $this->app->resolving(function ($object, $app) { // Called when container resolves object of any type... }); //这种监听使得该类在解析时,进行监听函数调用,比如添加一些额外属性啥的,然后才返回给consumer $this->app->resolving(FooBar::class, function (FooBar $fooBar, $app) { // Called when container resolves objects of type "FooBar"... });
5.Facades外立面
// 门面为应用的服务容器中的绑定类提供了一个“静态”接口。Laravel 内置了很多门面,你可能在不知道的情况下正在使用它们。Laravel 的门面作为服务容器中的底层类的“静态代理” // 在 Laravel 应用的上下文中,门面就是一个提供访问容器中对象的类。该机制原理由 Facade 类实现 // 门面类只需要实现一个方法:getFacadeAccessor。正是 getFacadeAccessor 方法定义了从容器中解析什么(然后 Facade 基类使用魔术方法 __callStatic() 从你的门面中调用解析对象) // Cache 门面继承 Facade 基类并定义了 getFacadeAccessor 方法,该方法的工作就是返回服务容器绑定类的别名,当用户引用 Cache 类的任何静态方法时,Laravel 从服务容器中解析 cache 绑定,然后在解析出的对象上调用所有请求方法(本例中是 get): php namespace App\Http\Controllers; use Cache; //注意我们在顶部位置引入了 Cache 门面。该门面作为代理访问底层 Illuminate\Contracts\Cache\Factory 接口的实现。我们对门面的所有调用都会被传递给 Laravel 缓存服务的底层实例。 use App\Http\Controllers\Controller; class UserController extends Controller{ /** * 为指定用户显示属性 * * @param int $id * @return Response */ public function showProfile($id) { $user = Cache::get('user:'.$id); return view('profile', ['user' => $user]); } } // Cache类的实现在Illuminate\Support\Facades\Cache class Cache extends Facade{ /** * 获取组件注册名称 * * @return string */ protected static function getFacadeAccessor() { return 'cache'; } } //下表列出了facade、底层类和container中的绑定名之间的关系: Facade Class Service Container Binding App Illuminate\Foundation\Application app Artisan Illuminate\Contracts\Console\Kernel artisan Auth Illuminate\Auth\AuthManager auth Blade Illuminate\View\Compilers\BladeCompiler blade.compiler Bus Illuminate\Contracts\Bus\Dispatcher Cache Illuminate\Cache\Repository cache Config Illuminate\Config\Repository config Cookie Illuminate\Cookie\CookieJar cookie Crypt Illuminate\Encryption\Encrypter encrypter DB Illuminate\Database\DatabaseManager db DB (Instance) Illuminate\Database\Connection Event Illuminate\Events\Dispatcher events File Illuminate\Filesystem\Filesystem files Gate Illuminate\Contracts\Auth\Access\Gate Hash Illuminate\Contracts\Hashing\Hasher hash Lang Illuminate\Translation\Translator translator Log Illuminate\Log\Writer log Mail Illuminate\Mail\Mailer mailer Password Illuminate\Auth\Passwords\PasswordBroker auth.password Queue Illuminate\Queue\QueueManager queue Queue (Instance) Illuminate\Contracts\Queue\Queue queue Queue (Base Class) Illuminate\Queue\Queue Redirect Illuminate\Routing\Redirector redirect Redis Illuminate\Redis\Database redis Request Illuminate\Http\Request request Response Illuminate\Contracts\Routing\ResponseFactory Route Illuminate\Routing\Router router Schema Illuminate\Database\Schema\Blueprint Session Illuminate\Session\SessionManager session Session (Instance) Illuminate\Session\Store Storage Illuminate\Contracts\Filesystem\Factory filesystem URL Illuminate\Routing\UrlGenerator url Validator Illuminate\Validation\Factory validator Validator (Instance) Illuminate\Validation\Validator View Illuminate\View\Factory view View (Instance) Illuminate\View\View
6.用户认证
// 配置文件位于config/auth.php // 在底层代码中,Laravel 的认证组件由“guards”和“providers”组成,Guard 定义了用户在每个请求中如何实现认证,例如,Laravel 通过 session guard来维护 Session 存储的状态、Cookie 以及 token guard,token guard 是认证用户发送请求时带的“API token”。 // Provider 定义了如何从持久化存储中获取用户信息 // 对应的数据表模型位于App\User,还有其对应的migration,位于database中 // Laravel 开箱提供了两个认证控制器,位于 App\Http\Controllers\Auth 命名空间下,AuthController 处理新用户注册和登录,PasswordController 用于帮助用户找回密码。每个控制器都使用 trait 来引入它们需要的方法。 use AuthenticatesAndRegistersUsers, ThrottlesLogins; // AuthController 中的自定义操作 // 自定义路径: protected $redirectTo = '/home'; // 自定义Guard,该属性的值对应认证配置文件 auth.php 中的相应 guard 配置 protected $guard = 'admin'; // 自定义验证/存储 AuthController 的 validator 方法包含了新用户的验证规则,你可以按需要自定义该方法。 AuthController 的 create 方法负责使用 Eloquent ORM 在数据库中创建新的 App\User 记录。当然,你也可以基于自己的需要自定义该方法。 // 获取认证用户 $user = Auth::user(); // 或通过Illuminate\Http\Request实例访问 php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Routing\Controller; class ProfileController extends Controller{ /** * 更新用户属性. * * @param Request $request * @return Response */ public function updateProfile(Request $request) { if ($request->user()) { // $request->user() 返回认证用户实例... } } } // 判断当前用户是否通过认证 if (Auth::check()) { // The user is logged in... } // auth中间件的使用,只允许通过认证的用户访问给定路由 // 使用路由闭包... Route::get('profile', ['middleware' => 'auth', function() { // 只有认证用户可以进入... }]); // 使用控制器... Route::get('profile', [ 'middleware' => 'auth', 'uses' => 'ProfileController@show' ]); // 在Controller中使用: public function __construct(){ $this->middleware('auth'); } // 指定一个Guard,在指定auth中间件时,也可以指定一个guard,确定其使用哪个(列表参见auth.php中的guards数组) Route::get('profile', [ 'middleware' => 'auth:api', 'uses' => 'ProfileController@show' ]); // 登录失败次数限制 如果你使用了 Laravel 内置的 AuthController 类, 可以使用 Illuminate\Foundation\Auth\ThrottlesLogins trait 来限制用户登录失败次数。默认情况下,用户在几次登录失败后将在一分钟内不能登录,这种限制基于用户的用户名/邮箱地址+IP地址。(这里如何设定次数???) // 这个类中,设为5次,应该也可以重写这个值 vendor/laravel/framework/src/Illuminate/Foundation/Auth/ThrottlesLogins.php // 通过ID认证用户,要通过用户ID登录到应用,可以使用 loginUsingId 方法,该方法接收你想要认证用户的主键作为参数(用于测试): Auth::loginUsingId(1); //一次性认证用户。你可以使用 once 方法只在单个请求中将用户登录到应用,而不存储任何 Session 和 Cookie,这在构建无状态的 API 时很有用。once方法和attempt方法用法差不多: if (Auth::once($credentials)) { // }
【下面的内容不看了,都是具体的接口函数、等等,用的时候再看】