Laravel 5项目结构分析及中文文档阅读摘要
HTTP路由 1
中间件 5
控制器 5
HTTP请求 7
HTTP 响应 8
视图 9
Service Providers 11
Service Container 12
Contracts 13
Facades 14
请求的生命周期 15
应用程序结构 16
认证 20
缓存 24
集合 26
Artisan Console 26
扩展框架* 27
Laravel Elixir* 27
加密 27
错误与日志 28
事件 28
文件系统 / 云存储 30
哈希 31
辅助方法 31
本地化* 31
邮件* 31
扩展包开发* 31
分页* 31
队列* 31
会话 32
Blade模板 33
单元测试* 35
数据验证 35
数据库使用基础 36
查询构造器 38
结构生成器 41
迁移和数据填充 41
Eloquent ORM 41
基本路由
定义针对不同Http Method的路由,如:
Route::get('/', function(){
Route::post('foo/bar', function(){
Route::match(['get', 'post'], '/', function(){ # 多个方法
Route::any('foo', function(){ # 所有方法
使用url方法生成url:$url = url('foo');
CSRF保护
Laravel会自动在每一位用户的session中放置随机的token。VerifyCsrfToken 中间件将保存在session中的请求和输入的token配对来验证token。除了寻找CSRF token 作为「POST」参数,中间件也检查X-XSRF-TOKEN请求头。
插入CSRF Token到表单:
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
在Blade模板引擎使用:
<input type="hidden" name="_token" value="{{ csrf_token() }}">
添加到X-XSRF-TOKEN请求头中:
<meta name="csrf-token" content="{{ csrf_token() }}" />
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
...
# 这样所有ajax请求中将会带上该头信息:
$.ajax({
url: "/foo/bar",
})
方法欺骗
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
路由参数
Route::get('user/{id}', function($id){ # 基础参数
Route::get('user/{name?}', function($name = null){ # 可选参数
Route::get('user/{name?}', function($name = 'John'){ # 带默认值的参数
可以定义参数的全局模式,在RouteServiceProvider的boot方法里定义模式:
$router->pattern('id', '[0-9]+');
之后,会作用在所有使用这个特定参数的路由上:
Route::get('user/{id}', function($id)
if ($route->input('id') == 1){ # 在路由外部取得参数
也可以通过依赖注入来取得参数:
use Illuminate\Http\Request;
Route::get('user/{id}', function(Request $request, $id){
if ($request->route('id')){
路由命名
Route::get('user/profile', ['as' => 'profile', function(){
# 为控制器动作指定路由名称
Route::get('user/profile', [
'as' => 'profile',
'uses' => 'UserController@showProfile'
]);
# 使用命名路由进行重定向
$url = route('profile');
$redirect = redirect()->route('profile');
# 返回当前路由请求的名称
$name = Route::currentRouteName();
路由群组
将共享属性作为一个数组当做Route::group第一个参数:
# 共享中间件
Route::group(['middleware' => ['foo', 'bar']], function()
{
Route::get('/', function()
{
// Has Foo And Bar Middleware
});
Route::get('user/profile', function()
{
// Has Foo And Bar Middleware
});
});
# 上例中foo和bar为中间件键名。自定义的中间件的键名与类名映射关系需要在Kernel.php中添加。
# 共享命名空间
Route::group(['namespace' => 'Admin'], function()
{
// Controllers Within The "App\Http\Controllers\Admin" Namespace
Route::group(['namespace' => 'User'], function()
{
// Controllers Within The "App\Http\Controllers\Admin\User" Namespace
});
});
子域名路由
Route::group(['domain' => '{account}.myapp.com'], function()
{
Route::get('user/{id}', function($account, $id)
{
//
});
});
路由前缀
Route::group(['prefix' => 'admin'], function()
{
Route::get('users', function()
{
// Matches The "/admin/users" URL
});
});
# 在路由前缀中定义参数
Route::group(['prefix' => 'accounts/{account_id}'], function()
{
Route::get('detail', function($account_id)
{
//
});
});
路由模型绑定
模型绑定提供方便的方式将模型实体注入到路由中:比起注入User ID,你可以选择注入符合给定ID的User类实体。在RouteServiceProvider::boot方法定义模型绑定:
public function boot(Router $router)
{
parent::boot($router);
$router->model('user', 'App\User');
}
然后定义一个有 {user} 参数的路由:
Route::get('profile/{user}', function(App\User $user){
//
});
请求至profile/1将注入ID为1的User实体。若实体不存在,则抛出404。可以传给第三个参数一个闭包,定义找不到时的行为。
抛出404错误
两种方法:
abort(404); # 本质上是抛出了一个带有特定状态码的Symfony\Component\HttpKernel\Exception\HttpException 。
或者:手工抛出HttpException
新建中间件
php artisan make:middleware OldMiddleware # 新建一个中间件
中间件的主要功能在handle()方法中实现:
class OldMiddleware {
public function handle($request, Closure $next){
if (xxx){
return redirect('xx');
}
return $next($request);
}
}
分析其结构可以发现,基本上就是执行一个判断,然后依次进行重定向或者继续向前。
全局中间件
若是希望中间件被所有的 HTTP 请求给执行,只要将中间件的类加入到app/Http/Kernel.php的$middleware 属性清单列表中。
指派中间件给路由
新建中间件后,在app/Http/Kernel.php的$routeMiddleware中添加中间件键名与类名的映射关系,然后就可以在路由中使用这个键名来指派路由:
Route::get('admin/profile', ['middleware' => 'auth', function(){
可终止中间件
可终止中间件需要继承自TerminableMiddleware,并实现terminate()方法。其用处是在HTTP响应已经被发送到用户端后再执行。可终止中间件需要添加到app/Http/Kernel.php的全局中间件清单中。
基础控制器
所有的控制器都应该扩展基础控制器类
<?php namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
class UserController extends Controller { # 继承Controller
public function showProfile($id) # 动作
{
App\Http\Controllers\Controller的定义如下:
<?php
namespace Borogadi\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
abstract class Controller extends BaseController
{
use DispatchesJobs, ValidatesRequests;
}
可见最终是继承自Illuminate\Routing\Controller类。
# 命名控制器路由
Route::get('foo', ['uses' => 'FooController@method', 'as' => 'name']);
# 指向控制器的URL
$url = action('App\Http\Controllers\FooController@method');
或者:
URL::setRootControllerNamespace('App\Http\Controllers');
$url = action('FooController@method');
# 获取正在执行的控制器动作名称
$action = Route::currentRouteAction();
控制器中间件
两种方式,一是在控制器路由中指定:
Route::get('profile', [
'middleware' => 'auth',
'uses' => 'UserController@showProfile'
]);
另一种是直接在控制器构造器中指定:
class UserController extends Controller {
public function __construct(){
$this->middleware('auth');
$this->middleware('log', ['only' => ['fooAction', 'barAction']]);
隐式控制器
隐式控制器实现定义单一路由来处理控制器中的每一项行为:
定义一个路由:
Route::controller('users', 'UserController');
定义控制器类的实现:
class UserController extends BaseController {
public function getIndex(){ # 响应user
public function postProfile(){ # 响应post方式的user/profile
public function anyLogin(){ # 响应所有方式的user/login
可以通过使用“-”来支持多个字词的控制器行为:
public function getAdminProfile() {} # 响应users/admin-profile,不是user/adminprofile。注意动作名中使用的驼峰命名方法
RESTful资源控制器
其实就是隐式控制器的一个具体应用。
路由缓存
如果应用中只使用了控制器路由,则可以利用路由缓存来提高性能。
php artisan route:cache
缓存路由文件将会被用来代替app/Http/routes.php文件
取得请求
两种方式,一是通过Request facade:
use Request;
$name = Request::input('name');
或者通过依赖注入:在控制器中的构造函数或方法对该类使用类型提示。当前请求的实例将会自动由服务容器注入:
<?php namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class UserController extends Controller {
public function store(Request $request){
$name = $request->input('name');
若同时还有使用路由参数输入的数据,只需将路由参数置于其他依赖之后:
public function update(Request $request, $id){
取得输入数据
$name = Request::input('name'); # 取得特定输入数据
$name = Request::input('name', 'Sally'); # 取得特定输入数据,若没有则取得默认值
if (Request::has('name')){ # 确认是否有输入数据
$input = Request::all(); # 取得所有输入数据
$input = Request::only('username', 'password'); # 取得部分输入数据
$input = Request::except('credit_card'); # 取得部分输入数据排除法
$input = Request::input('products.0.name'); # 取得数组形式的数据
旧输入数据
Request::flash(); # 将当前的输入数据存进 session中
Request::flashOnly('username', 'email'); # 将部分数据存成session
Request::flashExcept('password'); # 将部分数据存成session,排除法
return redirect('form')->withInput(); # 重定向,同时将当期输入数据缓存到session
return redirect('form')->withInput(Request::except('password')); # 重定向,同时将当期输入的部分数据缓存到session
$username = Request::old('username'); # 取得前一次请求所保存的一次性Session
{{ old('username') }} # 在blade模板中显示旧输入数据
Cookies
Laravel 所建立的 cookie 会加密并且加上认证记号。
$value = Request::cookie('name'); # 取得Cookie值
# 在响应中添加Cookies
$response = new Illuminate\Http\Response('Hello World');
$response->withCookie(cookie('name', 'value', $minutes));
$response->withCookie(cookie()->forever('name', 'value')); # 添加永久有效的Cookie
# 以队列方式添加Cookie,即在实际发送响应之前设置Cookie
Cookie::queue('name', 'value');
return response('Hello World');
上传文件
$file = Request::file('photo'); # 取得上传文件
if (Request::hasFile('photo')) # 确认文件是否有上传
if (Request::file('photo')->isValid()) # 确认上传的文件是否有效
Request::file('photo')->move($destinationPath); # 移动上传的文件
Request::file('photo')->move($destinationPath, $fileName); # 移动上传的文件,并重命名
其他的请求信息
$uri = Request::path(); # 取得请求 URI
if (Request::ajax()) # 判断一个请求是否使用了 AJAX
# 判断请求的方法
$method = Request::method();
if (Request::isMethod('post'))
if (Request::is('admin/*')) # 确认请求路径是否符合特定格式
$url = Request::url(); # 取得请求URL
基本响应
Route::get('/', function(){ # 返回字符串
return 'Hello World';
# 返回完整的Responses实例,有两种方法
返回Responses对象:
use Illuminate\Http\Response;
return (new Response($content, $status))
->header('Content-Type', $value);
或者使用response辅助方法:
return response($content, $status)->header('Content-Type', $value);
# 返回视图
return response()->view('hello')->header('Content-Type', $type);
# 添加Cookies
return response($content)->withCookie(cookie('name', 'value'));
重定向
return redirect('user/login'); # 使用redirect重定向方法
return redirect('user/login')->with('message', 'Login Failed'); # 重定向,并将当前数据保存至Session
return redirect()->back(); # 重定向至前一个位置
return redirect()->route('login'); # 重定向到特定路由
# 重定向到特定路由,并带参数
return redirect()->route('profile', [1]); # 路由的 URI 为:profile/{id}
return redirect()->route('profile', ['user' => 1]); # 路由的 URI 为:profile/{user}
# 根据控制器动作的重定向
return redirect()->action('App\Http\Controllers\HomeController@index');
return redirect()->action('App\Http\Controllers\UserController@profile', ['user' => 1]); # 带参数
其他响应
# 返回json
return response()->json(['name' => 'Abigail', 'state' => 'CA']);
# 返回jsonp
return response()->json(['name' => 'Abigail', 'state' => 'CA'])
->setCallback($request->input('callback'));
# 文件下载
return response()->download($pathToFile, $name, $headers);
响应宏
# 定义响应宏,通常定义在Provider的boot方法内
Response::macro('caps', function($value) use ($response){ # PHP在默认情况下,匿名函数不能调用所在代码块的上下文变量,而需要通过使用use关键字。use会复制一份变量到闭包内,也支持引用形式,如use ( &$rmb )
return $response->make(strtoupper($value));
});
# 调用响应宏
return response()->caps('foo');
基本视图
# 视图定义 文件路径及文件名:resources/views/greeting.php
<html>
<body>
<h1>Hello, <?php echo $name; ?></h1>
</body>
</html>
# 视图调用
Route::get('/', function()
{
return view('greeting', ['name' => 'James']); # 传给视图的参数为一个键值对数组
});
# 子文件夹视图调用 定义位置:resources/views/admin/profile.php
return view('admin.profile', $data);
# 传递数据到视图的其他方法
$view = view('greeting')->with('name', 'Victoria'); # 传统方法
$view = view('greeting')->withName('Victoria'); # 魔术方法
$view = view('greetings', $data); #直接传数组 $data为一个键值对数组
# 共享数据给所有视图
自定义一个Provider,或者直接在AppServiceProvider的boot方法内添加:
view()->share('data', [1, 2, 3]);
或者:
View::share('data', [1, 2, 3]);
# 确认视图是否存在
if (view()->exists('emails.customer'))
# 从一个文件路径产生视图
return view()->file($pathToFile, $data);
视图组件
视图组件就是在视图被渲染前,会调用的闭包或类方法。
# 定义视图组件
<?php namespace App\Providers;
use View;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider {
public function boot(){
View::composer('profile', 'App\Http\ViewComposers\ProfileComposer'); # 使用类来指定视图组件
View::composer('dashboard', function($view){ # 使用闭包来指定视图组件
...
});
}
...
}
使用类来指定视图组件,在视图被渲染之前将调用所指定类的名为compose的方法。如上例中,ProfileComposer'类的定义为:
<?php namespace App\Http\ViewComposers;
use Illuminate\Contracts\View\View;
use Illuminate\Users\Repository as UserRepository;
class ProfileComposer {
protected $users;
public function __construct(UserRepository $users){ # service container 会自动解析所需的参数
$this->users = $users;
}
public function compose(View $view){ # compose方法被传入了一个View的实例,在此可以传参给View
$view->with('count', $this->users->count());
}
}
# 在视图组件内使用通配符
View::composer('*', function($view){ # 相当于定义给所有视图
# 同时对多个视图附加视图组件
View::composer(['profile', 'dashboard'], 'App\Http\ViewComposers\MyViewComposer');
# 定义多个视图组件
View::composers([
'App\Http\ViewComposers\AdminComposer' => ['admin.index', 'admin.profile'],
'App\Http\ViewComposers\UserComposer' => 'user',
'App\Http\ViewComposers\ProductComposer' => 'product'
]);
每个自定义的Provider都必须继承自Illuminate\Support\ServiceProvider,并在config/app.php中的Providers数组中注册。自定义的Provider必须定义register()方法,用于定义注册时的行为。此外还有两个可选方法和一个可选属性:boot()方法在所有的Provider都被加载后才会调用,而provides()方法用来和$defer可选属性配合,提供缓载功能。
通过服务提供者的方式来提供服务的思路:实现一个完成实际工作的类,定义一个Provider,并在Provider的register()方法中往系统容器注册实际工作类以及获取实际工作类实例的方法。然后再在应用配置中注册这个Provider。这样,应用初始化时会调用所有Provider的register()方法来间接注册获取实际工作类实例的方式。
# 定义一个基本Provider
<?php namespace App\Providers;
use Riak\Connection;
use Illuminate\Support\ServiceProvider;
class RiakServiceProvider extends ServiceProvider {
public function register(){
# 往容器中注册一个类及获取其实例的方法
$this->app->singleton('Riak\Contracts\Connection', function($app){
return new Connection($app['config']['riak']);
});
}
}
基本用法
在Provider内部,可以通过$this->app来访问服务容器。
注册依赖主要有两种方式:回调接口方式和绑定实例接口。
# 闭包回调的方式
$this->app->bind('FooBar', function($app){
return new FooBar($app['SomethingElse']);
});
# 以单例方式注册,之后的调用都返回相同的实例
$this->app->singleton('FooBar', function($app){
return new FooBar($app['SomethingElse']);
});
# 绑定为一个已经存在的实例
$fooBar = new FooBar(new SomethingElse);
$this->app->instance('FooBar', $fooBar);
从容器解析出实例也有两种方式:
$fooBar = $this->app->make('FooBar'); # 使用make()方法解析
$fooBar = $this->app['FooBar']; # 因为容器实现了ArrayAccess接口,所以可以用数组访问形式
在定义好注册、解析信息后,就可以直接在类的构造函数中通过type-hint的方式指定所需要的依赖,容器将自动注入所需要的所有依赖。
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Users\Repository as UserRepository;
class UserController extends Controller {
protected $users;
public function __construct(UserRepository $users){ # type-hint
$this->users = $users;
}
public function show($id){
}
}
绑定接口
interface EventPusher {
public function push($event, array $data);
}
class PusherEventPusher implements EventPusher {
...
}
因为PusherEventPusher类实现了EventPusher接口,所以可以直接注册这个接口并绑定为某个实现了该接口的类:
$this->app->bind('App\Contracts\EventPusher', 'App\Services\PusherEventPusher');
当有类需要EventPusher接口时,会告诉容器应该注入PusherEventPusher。
上下文绑定
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give('App\Services\PubNubEventPusher');
标签
$this->app->bind('SpeedReport', function(){
});
$this->app->bind('MemoryReport', function(){
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports'); # 将上两步注册的类打成一个标签‘reports’
一旦服务打上标签,可以通过 tagged 方法轻易地解析它们:
$this->app->bind('ReportAggregator', function($app){
return new ReportAggregator($app->tagged('reports'));
});
容器事件
容器在解析每一个对象时就会触发一个事件。可以用resolving方法来监听此事件(被解析的对象将被传入到闭包方法中):
$this->app->resolving(function($object, $app){ # 当容器解析任意类型的依赖时被调用
...
});
$this->app->resolving(function(FooBar $fooBar, $app){ # 当容器解析’FooBar’类型的依赖时被调用
...
});
Contracts是所有Laravel主要组件实现所用的接口,可以看到Contracts目录下的目录结构和Illuminate中的一样。Contracts中为接口定义,Illuminate为具体实现。Illuminate中每个具体实现的类都扩展了其在Contracts中对应的接口。这样将接口和实现相分离,可以使依赖注入变得低耦合。
/laravel
/framework
/src
/Illuminate
/Auth
/Broadcasting
/Bus
...
/Contracts
/Auth
/Broadcasting
/Bus
...
基本用法
Facades提供一个静态接口给在应用程序的服务容器中可以取用的类。(设计模式中“装饰模式”的一个应用,主要是使用class_alias来创建类别名,另外使用__callStatic()来提供一个静态代理,其最终是使用模拟对象的方式模拟PHP对象并调用对象的方法)
Laravel的facades和你建立的任何自定义facades,将会继承基类Facade,并只需要去实现一个方法:getFacadeAccessor()。
如Cache这个facade的调用:$value = Cache::get('key');
看一下类的实现:
class Cache extends Facade {
protected static function getFacadeAccessor() { return 'cache'; } # 该方法的作用就是返回服务容器绑定的名称
}
当用户在Cache的facade上执行任何的静态方法,Laravel会从服务容器解析被绑定的cache ,并对该对象执行被请求的方法 (在这个例子中,get)
所有的facades存在于全局命名空间,当在有嵌套的命名空间中使用时,需要导入facade类进入命名空间:
<?php namespace App\Http\Controllers;
use Cache; # 导入Cache facade
class PhotosController extends Controller {
public function index(){
$photos = Cache::get('photos');
}
}
建立Facades
自定义一个facade需要3步:
1.一个服务容器绑定
2.一个facade类
3.一个facade别名配置
比如现有一个如下类:
namespace PaymentGateway;
class Payment {
public function process(){
//
}
}
首先绑定到服务容器: # 当然更好的实现方式是用一个Provider包装一下,然后把以下绑定添加到register()中
App::bind('payment', function(){
return new \PaymentGateway\Payment;
});
然后定义一个facade类:
use Illuminate\Support\Facades\Facade;
class Payment extends Facade {
protected static function getFacadeAccessor() { return 'payment'; }
}
最后在config/app.php配置文件为facade加个别名到aliases数组。
'aliases' => [
‘Payment’ =>PaymentGateway\Payment::class,
这时就可以调用该facade了:
Payment::process();
(1)通过服务器配置将public/index.php设为所有请求的进入点;
(2)index.php中通过require __DIR__.'/../bootstrap/autoload.php';加载所有需要与加载的项,在autoload.php中,通过如下调用实现:require __DIR__.'/../vendor/autoload.php';即加载了vendor目录下的所有由Composer管理的库。
(3)index.php接收由bootstrap/app.php文件所产生的Laravel应用程序实例:
index.php中:$app = require_once __DIR__.'/../bootstrap/app.php';
而bootstrap/app.php中文件最后:return $app;
再看一下Application类的实际定义:
class Application extends Container implements ApplicationContract, HttpKernelInterface
由此可知,Application实际就是一个Container。
查看一下Application的构造函数:
public function __construct($basePath = null){
$this->registerBaseBindings(); # 初始化Container环境
$this->registerBaseServiceProviders(); # 注册系统级事件服务和路由服务
$this->registerCoreContainerAliases(); # 添加系统所有facade别名关联
if ($basePath) {
$this->setBasePath($basePath);
}
}
(4)index.php中新建HTTP核心对象:$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);该核心对象是所有请求流向的中心位置。其构造函数签名如下:
public function __construct(Application $app, Router $router)
再看一下Application类中的make方法定义:
public function make($abstract, array $parameters = []){
if (isset($this->deferredServices[$abstract])) { # 注册缓载Providers
(5)HTTP核心对象中定义了一个bootstrappers数组,在http请求被执行前,会加载数组中定义的所有启动项,具体功能包括配置错误处理、日志记录、侦测应用程序环境等等;
(6)HTTP核心对象中定义了2个HTTP中间件清单(Illuminate\Foundation\Http\Kernel中是空数组,App\Http\Kernel类继承后在数组中加入了具体的项),$middleware数组定义全局的中间件,$routeMiddleware定义了特定路由的中间件。
(7)index.php中最终实际是调用了kernel的核心方法handle(),handle()方法接收一个Request,返回一个Response;
(8)handle方法中实际调用了路由分发等:$this->router->dispatch($request);
Laravel 5全面采用新的自动加载标准PSR-4。PSR-4是关于由文件路径自动载入对应类的相关规范。描述了完整类名、命名空间前缀、文件基目录、文件路径这4者之间的关系。详情可参考:https://github.com/PizzaLiu/PHP-FIG/blob/master/PSR-4-autoloader-cn.md
~/.composer/vendor/bin/laravel new borogadi 命令创建项目后,生成了如下结构的项目,目录结构相对旧版本有较大变化,Laravel 5项目目录结构及说明如下:
borogadi
/app
/Console
/Commands 命令对象,可以作为队列任务
/Inspire.php
/Kernel.php
/Events
/Event.php
/Exceptions
/Handler.php
/Http
/Controllers 控制器
/Auth
/AuthController.php
/PasswordController.php
/Controller.php
/Middleware 中间件(过滤器)
/Authenticate.php
/EncryptCookies.php
/RedirectIfAuthenticated.php
/VerifyCsrfToken.php 自动验证保存在session中的CSRF token与用户发送过来的是否一致(Laravel会自动在每一位用户的session中放置随机的token,用于防止跨站伪造请求攻击)。具体验证行为主要是两个,一个是post数据中的_token值(表单),另一个是X-XSRF-TOKEN请求头(ajax)。此外,Laravel会在Cookies中放置这个值。
/Requests 请求
/Requests.php
/Kernel.php 定义应用程序全局中间件列表($middleware),以及不同路由需要执行的中间件列表($routeMiddleware)
/routes.php 路由定义文件,被/Providers/RouteServiceProvider.php文件所require
/Jobs
/Job.php
/Listeners
/Providers 服务提供者,全都继承自ServiceProvider 自定义的Provider可以考虑放在这里
/AppServiceProvider.php
/EventServiceProvider.php
/RouteServiceProvider.php 继承自Illuminate\Foundation\Support\Providers\RouteServiceProvider,并require了/app/Http/routes.php文件
/Users.php
/bootstrap
/cache
services.json
/app.php
/autoload.php
/config 项目配置
可以使用Config facade来获取或设置配置,如:Config::get('app.timezone'); 也可以使用config方法:$value = config('app.timezone');
可以使用 Artisan 命令config:cache将所有的配置文件缓存到单一文件。通过命令会将所有的配置选项合并成一个文件,让框架能够快速加载。
/app.php 应用程序配置,主要包括:
是否打开调试模式
时区、地理位置等本地化配置
providers 所有用到的服务提供者
aliases 所有类别名定义
/auth.php
/broadcasting.php
/cache.php
/compile.php
/database.php
/filesystems.php
/mail.php
/queue.php
/services.php
/session.php
/view.php
/database
/factories
/ModelFactory.php
/migrations
/2014_10_12_000000_create_users_table.php
/2014_10_12_100000_create_password_resets_table.php
/seeds
/DatabaseSeeder.php
/public
/.htaccess 分布式配置文件,提供了针对目录改变配置的方法,可以通过Apache的AllowOverride指令来设置是否启用
/favicon.ico
/index.php
/robots.txt
/resources 资源文件,包括视图、语言文件等
/assets
/sass
/app.scss .scss文件是sass工具使用的文件。sass是一个用来开发CSS的工具,可以像编程一样来开发CSS(生成的文件后缀名就是.scss),然后编译转换为实际的CSS文件
/lang
/en 在/config/app.php中可以指定 'locale' => 'en'等本地化设置
/auth.php
/pagination.php
/passwords.php
/validation.php
/view
/errors
/503.blade.php 维护模式响应视图,当应用程序处于维护模式中,将不会处理任何队列工作。所有的队列工作将会在应用程序离开维护模式后继续被进行。
启用网站维护模式:php artisan down
关闭网站维护模式:php artisan up
/vendor
/welcome.blade.php
/storage
/app
/framework
/cache
/sessions
/e974577fcae1531911b8e227f5b3041fcf2da5cf
/views
/74ea641521c4c2e25fef87398795c7c3
/logs
/tests
/ExampleTest.php
/TestCase.php
/vendor
/bin
/composer
/danieIstjules
/dnoegel
/doctrine
/fzaninotto
/hamcrest
/jakub-onderka
/jeremeamia
/laravel
/framework
/src
/Illuminate
/Auth
/Broadcasting
/Bus
...
/Contracts 所有Laravel主要组件实现所用的接口,可以看到Contracts目录下的目录结构和Illuminate中的一样。Contracts中为接口定义,Illuminate为具体实现。Illuminate中每个具体实现的类都扩展了其在Contracts中对应的接口。这样将接口和实现相分离,可以使依赖注入变得低耦合。
/Auth
/Broadcasting
/Bus
...
/league
/mockery
/monolog
/mtdowling
/nesbot
/nikic
/phpdocumentor
/phpspec
/phpunit
/psr
/psy
/sebastian
/swiftmailer
/symfony
/vlucas
autoload.php
/artisan
/composer.json
/composer.lock
/gulpfile.js
/package.json
/phpspec.xml
/sever.php
/.env 本地环境配置,该文件是在版本控制之外的。主要定义:APP_DEBUG、APP_KEY、CACHE_DRIVER、SESSION_DRIVER等等
Laravel通过DotEnv(https://github.com/vlucas/phpdotenv)写的一个类库来加载.env中的配置,当应用程序收到请求,.env中所有的变量都会被加载到$_ENV这个超级全局变量中,然后可以使用env()这个辅助方法来查看,如:'debug' => env('APP_DEBUG', false),
/.env.example
总结一下:
(1)控制器位置:/app/Http/Controllers
(2)视图位置:/app/resources/views
()路由定义:app/Http/routes.php
(3)需要有写入权限的目录和文件:
1)/storage
2)/vendor
3)/bootstrap/cache
(4)配置加载流程:
1).env中为版本控制之外的环境配置,通过env()方法来访问
2)/config目录中为各个模块的配置,通过Config facade或者config方法来访问
()注册Provide位置:config/app.php中的Provides数组
认证服务模型
Laravel提供了一个认证服务,配置文件为config/auth.php,需要和满足特定条件的数据库表了配合使用。在app/User.php文件中定义了User模型:
class User extends Model implements AuthenticatableContract, CanResetPasswordContract{
protected $table = 'users'; # User模型所映射的数据库表名
protected $fillable = ['name', 'email', 'password']; # 数据库字段
protected $hidden = ['password', 'remember_token']; # 以json形式返回用户信息时,需要排除的字段
}
模型所映射的数据库表(比如users)必须满足以下条件:
1.password列的长度至少为60
2.必须包含一个名为remember_token的列,该列可为空,长度为100。这个列将用来存储session token。
控制器
Laravel提供了2个有关用户认证的控制器,都在app/Http/Controllers/Auth目录下:AuthController用来处理新用户注册,PasswordController用来处理已注册用户的密码重置。这2个控制器通过引入trait的方式进行工作,一般不需要做任何修改。
路由
需要手动往app/Http/routes.php文件添加指向到认证控制的路由,如:
# 认证路由
Route::get('auth/login', 'Auth\AuthController@getLogin');
Route::post('auth/login', 'Auth\AuthController@postLogin');
Route::get('auth/logout', 'Auth\AuthController@getLogout');
# 注册路由
Route::get('auth/register', 'Auth\AuthController@getRegister');
Route::post('auth/register', 'Auth\AuthController@postRegister');
视图
需要手工实现登录和注册的视图:
# 用户登录视图
resources/views/auth/login.blade.php
<form method="POST" action="/auth/login">
{!! csrf_field() !!}
<div>
<input type="email" name="email" value="{{ old('email') }}">
</div>
<div>
Password
<input type="password" name="password" id="password">
</div>
<div>
<input type="checkbox" name="remember"> Remember Me
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
# 用户注册视图
<form method="POST" action="/auth/register">
{!! csrf_field() !!}
<div>
Name
<input type="text" name="name" value="{{ old('name') }}">
</div>
<div>
<input type="email" name="email" value="{{ old('email') }}">
</div>
<div>
Password
<input type="password" name="password">
</div>
<div>
Confirm Password
<input type="password" name="password_confirmation">
</div>
<div>
<button type="submit">Register</button>
</div>
</form>
流程
当用户成功登录后,默认会重定向至/home这个路由,所以需要在路由配置中添加这一条路由。也可以在AuthController中重写redirectPath属性来修改默认的设置,如:
protected $redirectPath = '/dashboard'; # 用户登录后重定向至/dashboard
当用户登录失败时,默认会重定向至/auth/login这个路由,可以通过重写AuthController中的loginPath属性来修改:
protected $loginPath = '/login'; # 用户登录失败后重定向至/login
自定义
要自定义认证行为,需要修改AuthController的validator方法。
要自定义注册行为(比如添加其他字段),需要修改AuthController的create方法。
获取登录用户
$user = Auth::user(); # 使用Auth facade获取登录用户
class ProfileController extends Controller{
public function updateProfile(Request $request){
if ($request->user()) { # 通过Request对象来获取登录用户
..
}
}
}
判断用户是否登录
if (Auth::check()) {
// The user is logged in...
}
保护路由
保护路由中间件auth定义在app\Http\Middleware\Authenticate.php文件中,可以通过如下方式为需要登录保护的路由添加该中间件:
# 为普通路由添加
Route::get('profile', ['middleware' => 'auth', function() {
...
}]);
# 为控制器路由添加
Route::get('profile', [
'middleware' => 'auth',
'uses' => 'ProfileController@show'
]);
# 直接在控制器的构造函数中添加
public function __construct(){
$this->middleware('auth');
}
认证限制
如果使用Laravel自带的AuthController,可以使用Illuminate\Foundation\Auth\ThrottlesLogins 这个trait来限制登录次数。它将根据唯一的用户名、email、ip地址来进行限制:当数次登录尝试失败后,一分钟内将不能再进行登录操作。
class AuthController extends Controller{
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
}
手工进行用户认证
class AuthController extends Controller{
public function authenticate(){
if (Auth::attempt(['email' => $email, 'password' => $password])) { # 会查找指定email值的用户,若找到了,则再比较password的hash值。
// Authentication passed...
return redirect()->intended('dashboard');
}
}
}
# 添加认证条件:active为1
if (Auth::attempt(['username' => $username, 'password' => $password, 'active' => 1])) {
// The user is active, not suspended, and exists.
}
# 注销登录
Auth::logout();
记住用户
需要数据库包含remember_token列,并在调用attempt方法时传入一个$remember参数。这样,除非用户手工进行注销,否则将会记住用户的登陆态。
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// The user is being remembered...
}
在使用了记住用户的情况下获取用户信息:
if (Auth::viaRemember()) {
//
}
重置密码
1.用户类必须实现了Illuminate\Contracts\Auth\CanResetPassword接口,且使用了Illuminate\Auth\Passwords\CanResetPassword这个trait(自带的User类已经实现)。
2.生成一个数据库表来存储重设密码标志。Laravel默认已经包含了这个迁移表,放在database/migrations目录下。通过如下命令执行迁移:
php artisan migrate
3.往Auth\PasswordController中添加路由:
// Password reset link request routes...
Route::get('password/email', 'Auth\PasswordController@getEmail');
Route::post('password/email', 'Auth\PasswordController@postEmail');
// Password reset routes...
Route::get('password/reset/{token}', 'Auth\PasswordController@getReset');
Route::post('password/reset', 'Auth\PasswordController@postReset');
4.重置密码视图 resources/views/auth/password.blade.php
<form method="POST" action="/password/email">
{!! csrf_field() !!}
<div>
<input type="email" name="email" value="{{ old('email') }}">
</div>
<div>
<button type="submit">
Send Password Reset Link
</button>
</div>
</form>
5.重置密码邮件视图 resources/views/emails/password.blade.php
Click here to reset your password: {{ url('password/reset/'.$token) }}
6.重置密码确认视图 resources/views/auth/reset.blade.php
<form method="POST" action="/password/reset">
{!! csrf_field() !!}
<input type="hidden" name="token" value="{{ $token }}">
<div>
<input type="email" name="email" value="{{ old('email') }}">
</div>
<div>
<input type="password" name="password">
</div>
<div>
<input type="password" name="password_confirmation">
</div>
<div>
<button type="submit">
Reset Password
</button>
</div>
</form>
7.可以设置PasswordController的$redirectTo属性来指定重设密码后的重定向路由:
protected $redirectTo = '/dashboard';
第三方登录认证
Laravel提供第三方认证模块进行OAuth认证,目前支持的第三方认证有:Facebook, Twitter, LinkedIn, Google, GitHub and Bitbucket.
缓存方法
Laravel为各种不同的缓存系统提供一致的API。缓存配置文件位在config/cache.php 。
# 保存对象到缓存中
Cache::put('key', 'value', $minutes);
# 使用Carbon对象配置缓存过期时间
$expiresAt = Carbon::now()->addMinutes(10);
Cache::put('key', 'value', $expiresAt);
# 若是对象不存在,则将其存入缓存中,当对象确实被加入缓存时,使用add方法将会返回true否则会返回false 。
Cache::add('key', 'value', $minutes);
# 确认对象是否存在
if (Cache::has('key'))
# 从缓存中取得对象
$value = Cache::get('key');
# 取得对象或是返回默认值
$value = Cache::get('key', 'default');
$value = Cache::get('key', function() { return 'default'; });
# 永久保存对象到缓存中
Cache::forever('key', 'value');
# 从缓存中取得一个对象,若不存在则存入一个值。所有保存在缓存中的对象皆会被序列化
$value = Cache::remember('users', $minutes, function(){
return DB::table('users')->get();
});
# 结合remember和forever方法
$value = Cache::rememberForever('users', function(){
return DB::table('users')->get();
});
# 从缓存中取得对象后将它删除
$value = Cache::pull('key');
# 从缓存中删除对象
Cache::forget('key');
# 获取特定的缓存存储(应用于使用多种缓存进行存储时)
$value = Cache::store('foo')->get('key');
递增与递减
除了数据库以外的缓存系统都支持递增和递减操作
# 递增
Cache::increment('key');
Cache::increment('key', $amount);
# 递减
Cache::decrement('key');
Cache::decrement('key', $amount);
缓存标签
文件或数据库这类缓存系统均不支持缓存标签。此外,使用带有「forever」的缓存标签时,挑选memcached这类缓存系统将获得最好的性能,它会自动清除过期的纪录。
缓存事件
Event::listen('cache.hit', function($key, $value) {
});
Event::listen('cache.missed', function($key) {
});
Event::listen('cache.write', function($key, $value, $minutes) {
});
Event::listen('cache.delete', function($key) {
});
建立集合
$collection = collect([1, 2, 3]); # 使用collect方法
$collection = Collection::make([1, 2, 3]); # 使用Collection类的make方法
Illuminate\Support\Collection类对数组进行了封装,简化数组操作,如查询、合并、去重、tojson等。
编写命令
自定义的命令都保存在app/Console/Commands目录下。
# 新建一个命令
php artisan make:console SendEmails
这将会新建一个app/Console/Commands/SendEmails.php文件。
# 新建一个命令,同时为其分配一个命令名
php artisan make:console SendEmails --command=emails:send
命令结构
<?php
namespace App\Console\Commands;
use App\User;
use App\DripEmailer;
use Illuminate\Console\Command;
use Illuminate\Foundation\Inspiring;
class Inspire extends Command{
protected $signature = 'email:send {user}'; # 使用artisan list时显示的信息
protected $description = 'Send drip e-mails to a user'; # 使用artisan list时显示的信息
protected $drip;
public function __construct(DripEmailer $drip){
parent::__construct();
$this->drip = $drip;
}
public function handle(){ # 命令执行的主方法
$this->drip->send(User::find($this->argument('user')));
}
}
命令输入/输出
注册命令
往app/Console/Kernel.php中的$commands数组中添加命令类:
protected $commands = [
'App\Console\Commands\SendEmails'
];
从代码中调用命令
可以从代码中调用命令,并传入参数。可以指定将命令添加到队列中执行。
此外,也可以从一个命令中调用另一个命令。
前置条件:确保config/app.php文件中的key选项配置了16, 24,或32字符的随机字串,否则加密的数值不会安全。
$encrypted = Crypt::encrypt('secret'); # 加密
$decrypted = Crypt::decrypt($encryptedValue); # 解密
配置
日志功能的配置在Illuminate\Foundation\Bootstrap\ConfigureLogging启动类中,这个类通过加载config/app.php配置文件的log配置选项进行配置。
'log' => 'single' # 配置为单一日志文件
'log' => ‘daily’ # 配置为每天一个日志文件
config/app.php配置文件的app.debug配置选项控制应用程序透过浏览器显示错误细节。配置选项默认参照.env文件的APP_DEBUG环境变量。
错误处理
所有的异常都由app\Exceptions\Handler类处理,该类有如下2个方法和1个属性:
report方法只基本实现简单地传递异常到父类并于父类记录异常。
render方法负责把异常转换成应该被传递回浏览器的HTTP 响应。
$dontReport属性是个数组,包含应该不要被纪录的异常类型。
具体可以结合instanceof来重写,以便对各种异常进行分类处理。
HTTP异常
默认在Handler类中忽略了对HTTP异常的处理。
在整个生命周期中,可以随时抛出HTTP异常,如abort(404);
可以在resources/views/errors/404.blade.php文件中自定义404错误页面的视图。
日志
Laravel默认为应用程序建立每天的日志文件在storage/logs目录,可以如下写入日志:
Log::info('This is some useful information.');
Log::warning('Something could be going wrong.');
Log::error('Something is really going wrong.');
注册事件和监听类
事件类都放在app/Events目录下,事件监听类都放在app/Listeners目录下。
在app/Providers/EventServiceProvider.php的$listen属性中添加事件及其对应监听类的映射关系:
protected $listen = [
'App\Events\PodcastWasPurchased' => [
'App\Listeners\EmailPurchaseConfirmation',
],
];
可以先在app/Providers/EventServiceProvider.php中列出所有的event和listener的映射关系,然后用以下命令来生成event和listener类文件:
php artisan event:generate
已经存在的event和listener不会被修改。
定义事件
一个事件类其实就是一个数据容器,携带一些和事件相关的数据,不包含逻辑内容。如:
class PodcastWasPurchased extends Event{
use SerializesModels;
public $podcast;
public function __construct(Podcast $podcast){
$this->podcast = $podcast;
}
}
定义监听器
class EmailPurchaseConfirmation{
public function __construct(){
//
}
public function handle(PodcastWasPurchased $event){
// Access the podcast using $event->podcast...
}
}
事件监听器都是通过服务容器进行解析的,因此可以在构造方法中自定义需要的type-hint。
要停止事件传播(不继续传播到其他监听器),需要在handle方法中返回false。
队列化事件监听
将监听器类标记为实现ShouldQueue接口,则该监听器将支持队列化行为。当接收到事件时,它将被自动被Laravel的事件分发器添加到执行队列中。当该监听器任务被成功执行,将被从任务队列中删除。
class EmailPurchaseConfirmation implements ShouldQueue{
//
}
如果想要操作底层任务队列的delete和release方法,需要借助Illuminate\Queue\InteractsWithQueue trait:
class EmailPurchaseConfirmation implements ShouldQueue{
use InteractsWithQueue;
public function handle(PodcastWasPurchased $event){
if (true) {
$this->release(30);
}
}
}
发出事件
$response = Event::fire(new PodcastWasPurchased($podcast)); # 使用Event facade触发
event(new PodcastWasPurchased($podcast)); # 使用event()方法
广播事件
Laravel支持通过websocket向客户端发出事件广播,这样就可以在服务器端和客户端的js代码中使用相同的事件名。
事件订阅者
事件订阅者可以同时订阅多个事件,如下:
class UserEventHandler {
public function onUserLogin($event){
//
}
public function onUserLogout($event){
//
}
public function subscribe($events){
$events->listen('App\Events\UserLoggedIn', 'UserEventHandler@onUserLogin'); # 由自己的onUserLogin方法处理
$events->listen('App\Events\UserLoggedOut', 'UserEventHandler@onUserLogout'); # 由自己的onUserLogout方法处理
}
}
定义了事件订阅者后,需要在EventServiceProvider中的$subscribe属性中进行注册:
protected $subscribe = [
'App\Listeners\UserEventListener',
];
Laravel基于Flysystem实现了对本地文件系统、云存储访问的相同的API。文件系统的配置文件放在 config/filesystems.php中,使用本地空间时,所有的操作路径都是相对于配置文件里的local配置项的root的值。
通过Storage facade来访问:
$disk = Storage::disk('local'); # 取得本地硬盘
if (Storage::exists('file.jpg')) # 判断文件是否存在
$contents = Storage::get('file.jpg'); # 获取文件内容
Storage::put('file.jpg', $contents); # 设置文件内容
Storage::prepend('file.log', 'Prepended Text'); # 添加内容到文件开始处
Storage::append('file.log', 'Appended Text'); # 添加内容到文件结尾处
Storage::delete('file.jpg'); # 删除文件
Storage::delete(['file1.jpg', 'file2.jpg']); # 批量删除文件
Storage::copy('old/file1.jpg', 'new/file1.jpg'); # 复制文件
Storage::move('old/file1.jpg', 'new/file1.jpg'); # 移动文件
$size = Storage::size('file1.jpg'); # 获取文件大小
$time = Storage::lastModified('file1.jpg'); # 获取最近修改时间
$files = Storage::files($directory); # 取得当前目录下所有文件
$files = Storage::allFiles($directory); # 递归取得当前目录下所有文件
$directories = Storage::directories($directory); # 取得当前目录下所有子目录
$directories = Storage::allDirectories($directory); # 递归取得当前目录下所有子目录
Storage::makeDirectory($directory); # 新建目录
Storage::deleteDirectory($directory); # 删除目录
Laravel的Hash使用Bcrypt进行加密。
$password = Hash::make('secret'); # 加密
$password = bcrypt('secret'); # 直接使用函数加密
if (Hash::check('secret', $hashedPassword)) # 验证
if (Hash::needsRehash($hashed)) # 检查是否需要重新加密
Laravel扩展实现的一些方法:
数组方法
路径
字符串
URLs
杂项函数
(略)
(用到再看)
(开发插件,暂时用不到,略)
(实际应该会自己开发,用到再看)
(需要与各种队列驱动扩展包配合使用,用到再看)
Laravel支持多种session后端驱动(文件、Memcached、Redis),并通过清楚、统一的API提供使用。session 的配置文件配置在config/session.php中。
如果需要加密所有的session数据,就将选项encrypt配置为true。当使用cookie作为session驱动时,永远不应该从 HTTP Kernel中移除EncryptCookie中间件。如果你移除了这个中间件,你的应用容易遭受远程代码攻击。
使用session
# 保存对象到session中
Session::put('key', 'value'); # 使用facade
session(['key' => 'value']); # 使用session方法
# 保存对象进session数组值中
Session::push('user.teams', 'developers');
# 从session取回对象
$value = Session::get('key'); # 使用facade
$value = session('key'); # 使用session方法
# 从Session取回对象,若无则返回默认值
$value = Session::get('key', 'default');
$value = Session::get('key', function() { return 'default'; });
# 从Session取回对象,并删除
$value = Session::pull('key', 'default');
# 从Session取出所有对象
$data = Session::all();
# 判断对象在 Session 中是否存在
if (Session::has('users'))
# 从 Session 中移除对象
Session::forget('key');
# 清空所有 Session
Session::flush();
# 重新产生 Session ID
Session::regenerate();
暂存数据
Session::flash('key', 'value'); # 暂存一些数据,并只在下次请求有效。
Session::reflash(); # 刷新当前暂存数据,延长到下次请求
Session::keep(['username', 'email']); # 只刷新指定快闪数据
数据库Sessions
当使用database session驱动时,必需建置一张保存 session 的数据表:
Schema::create('sessions', function($table){
$table->string('id')->unique();
$table->text('payload');
$table->integer('last_activity');
});
所有的Blade模板后缀名都要命名为.blade.php
# 定义一个 Blade 页面布局 resources/views/layouts/master.blade.php
<html>
<head>
<title>App Name - @yield('title')</title>
</head>
<body>
@section('sidebar')
This is the master sidebar.
@show
<div class="container">
@yield('content')
</div>
</body>
</html>
# 在视图模板中使用Blade页面布局
@extends('layouts.master') # 继承layouts/master.blade.php
@section('title', 'Page Title')
@section('sidebar') # @section ... @stop用来替换继承模板中的@section...@show
@parent
<p>This is appended to the master sidebar.</p>
@stop
@section('content')
<p>This is my body content.</p>
@stop
如果视图继承(extend)了一个Blade页面布局会将页面布局中定义的区块用视图中所定义的区块重写。如果想要将页面布局中的区块内容也能在继承此布局的视图中呈现,那就要在区块中使用 @parent 语法指令
# 在Blade视图中打印(Echoing)数据
Hello, {{ $name }}.
The current UNIX timestamp is {{ time() }}.
# 检查数据是否存在后再打印数据
{{ isset($name) ? $name : 'Default' }}
或者:
{{ $name or 'Default' }}
# 使用花括号显示文字
@{{ This will not be processed by Blade }}
# 阻止数据解析
Hello, {!! $name !!}.
# If声明
@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif
@unless (Auth::check())
You are not signed in.
@endunless
# 循环
@for ($i = 0; $i < 10; $i++)
The current value is {{ $i }}
@endfor
@foreach ($users as $user)
<p>This is user {{ $user->id }}</p>
@endforeach
@forelse($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>No users</p>
@endforelse
@while (true)
<p>I'm looping forever.</p>
@endwhile
# 加载子视图
@include('view.name')
@include('view.name', ['some' => 'data'])
# 重写区块
@extends('list.item.container')
@section('list.item.content')
<p>This is an item of type {{ $item->type }}</p>
@overwrite
# 注释
{{-- This comment will not be in the rendered HTML --}}
(略)
可以手工新建Validation类的对象来进行基本的验证。对于控制器,可以借助Controller类使用的ValidatesRequests trait来简化操作。对于复杂的验证操作,可以实现一个request类,借助依赖注入的方式来简化。
基本验证
$validator = Validator::make(
['name' => 'Dayle'],
['name' => 'required|min:5']
);
使用数组来定义规则
$validator = Validator::make(
['name' => 'Dayle'],
['name' => ['required', 'min:5']]
);
验证多个字段
$validator = Validator::make(
[
'name' => 'Dayle',
'password' => 'lamepassword',
'email' => '[email protected]'
],
[
'name' => 'required',
'password' => 'required|min:8',
'email' => 'required|email|unique:users'
]
);
# 判断验证是否通过
if ($validator->fails())
if ($validator->passes())
# 从验证器中接收错误信息
$messages = $validator->messages();
# 取得无法通过验证的规则
$failed = $validator->failed();
# 在完成验证后增加回调函数
$validator = Validator::make(...);
$validator->after(function($validator){
//
});
if ($validator->fails()){
//
}
控制器验证
# 借助Controller类使用的ValidatesRequests trait来简化操作
public function store(Request $request){
$this->validate($request, [
'title' => 'required|unique|max:255',
'body' => 'required',
]);
}
如果验证通过了,代码会正常继续执行。如果验证失败,那么会抛出一个 Illuminate\Contracts\Validation\ValidationException 异常。这个异常会被自动捕获,然后重定向至用户上一个页面。而错误信息甚至已经存储至 session 中!
如果收到的是一个 AJAX 请求,那么不会生成一个重定向。相反的,一个带有 422 状态码的 HTTP 响应会被返回给浏览器,包含了一个含有错误信息的 JSON 对象。
使用错误信息
echo $messages->first('email'); # 查看一个字段的第一个错误信息
foreach ($messages->get('email') as $message) # 查看一个字段的所有错误信息
foreach ($messages->all() as $message) # 查看所有字段的所有错误信息
if ($messages->has('email')) # 判断一个字段是否有错误信息
echo $messages->first('email', '<p>:message</p>'); # 错误信息格式化输出
数据库配置文件位置:config/database.php
# 使用特定数据库连接进行SELECT操作,同时使用另外的连接进行INSERT、UPDATE、以及DELETE操作。
'mysql' => [
'read' => [
'host' => '192.168.1.1',
],
'write' => [
'host' => '196.168.1.2'
],
'driver' => 'mysql',
'database' => 'database',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
],
执行查找
$results = DB::select('select * from users where id = ?', [1]); # select
$results = DB::select('select * from users where id = :id', ['id' => 1]); # select 使用名称绑定
DB::insert('insert into users (id, name) values (?, ?)', [1, 'Dayle']); # insert
DB::update('update users set votes = 100 where name = ?', ['John']); # update
DB::delete('delete from users'); # delete
DB::statement('drop table users'); # 执行一般语句
数据库事务处理
# 使用transaction
DB::transaction(function()
{
DB::table('users')->update(['votes' => 1]);
DB::table('posts')->delete();
});
# 手工抛出
DB::beginTransaction();
DB::rollback();
DB::commit();
获取连接
$users = DB::connection('foo')->select(...); # 使用特定连接获取(应用于多种连接的情况)
$pdo = DB::connection()->getPdo(); # 获取底层PDO实例
DB::reconnect('foo'); # 重新连接到特定的数据库
DB::disconnect('foo'); # 关闭特定的数据库连接
查找日志记录
DB::connection()->enableQueryLog(); # 启用日志
$queries = DB::getQueryLog(); # 得到执行过的查找纪录数组
Laravel 查询构造器使用 PDO 参数绑定,以保护应用程序免于SQL注入,因此传入的参数不需额外转义特殊字符。
Selects
# 从数据表中取得所有的数据列
$users = DB::table('users')->get();
foreach ($users as $user){
var_dump($user->name);
}
# 从数据表中分块查找数据列
DB::table('users')->chunk(100, function($users){
foreach ($users as $user){
// return false; # 停止处理接下来的数据列
}
});
# 从数据表中取得单一数据列
$user = DB::table('users')->where('name', 'John')->first();
var_dump($user->name);
# 从数据表中取得单一数据列的单一字段
$name = DB::table('users')->where('name', 'John')->pluck('name');
# 取得单一字段值的列表
$roles = DB::table('roles')->lists('title');
$roles = DB::table('roles')->lists('title', 'name'); # 为返回的数组指定自定义键值
# 指定查询子句
$users = DB::table('users')->select('name', 'email')->get();
$users = DB::table('users')->distinct()->get();
$users = DB::table('users')->select('name as user_name')->get();
# 增加查询子句到现有的查询中
$query = DB::table('users')->select('name');
$users = $query->addSelect('age')->get();
# 使用where及运算符
$users = DB::table('users')->where('votes', '>', 100)->get();
# 「or」语法
$users = DB::table('users')
->where('votes', '>', 100)
->orWhere('name', 'John')
->get();
# Where Between
$users = DB::table('users')->whereBetween('votes', [1, 100])->get();
# Where Not Between
$users = DB::table('users')->whereNotBetween('votes', [1, 100])->get();
# Where In与数组
$users = DB::table('users')->whereIn('id', [1, 2, 3])->get();
$users = DB::table('users')->whereNotIn('id', [1, 2, 3])->get();
# Where Null
$users = DB::table('users')->whereNull('updated_at')->get();
# Order By、Group By及Having
$users = DB::table('users')
->orderBy('name', 'desc')
->groupBy('count')
->having('count', '>', 100)
->get();
# Offset、Limit
$users = DB::table('users')->skip(10)->take(5)->get();
Joins
# 基本的Join语法
DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.id', 'contacts.phone', 'orders.price')
->get();
# Left Join语法
DB::table('users')
->leftJoin('posts', 'users.id', '=', 'posts.user_id')
->get();
# 在join中使用where型式的子句
DB::table('users')
->join('contacts', function($join)
{
$join->on('users.id', '=', 'contacts.user_id')
->where('contacts.user_id', '>', 5);
})
->get();
聚合
$users = DB::table('users')->count();
$price = DB::table('orders')->max('price');
$price = DB::table('orders')->min('price');
$price = DB::table('orders')->avg('price');
$total = DB::table('users')->sum('votes');
原生表达式
$users = DB::table('users')
->select(DB::raw('count(*) as user_count, status')) # 要小心勿建立任何SQL注入点
->where('status', '<>', 1)
->groupBy('status')
->get();
添加
# 添加数据进数据表
DB::table('users')->insert(
['email' => '[email protected]', 'votes' => 0]
);
# 添加自动递增ID的数据至数据表,如果数据表有自动递增的ID,可以使用insertGetId 添加数据并返回该 ID
$id = DB::table('users')->insertGetId(
['email' => '[email protected]', 'votes' => 0]
);
# 添加多个数据进数据表
DB::table('users')->insert([
['email' => '[email protected]', 'votes' => 0],
['email' => '[email protected]', 'votes' => 0]
]);
更新
# 更新数据表中的数据
DB::table('users')->where('id', 1)->update(['votes' => 1]);
# 自增或自减一个字段的值
DB::table('users')->increment('votes');
DB::table('users')->increment('votes', 5);
DB::table('users')->decrement('votes');
DB::table('users')->decrement('votes', 5);
删除
# 删除数据表中的数据
DB::table('users')->where('votes', '<', 100)->delete();
# 删除数据表中的所有数据
DB::table('users')->delete();
# 清空数据表
DB::table('users')->truncate();
(提供一个与数据库无关的数据表产生方法)
(和结构生成器一起对数据库进行版本控制)
基本用法
模型通常放在app目录下,所有的模型都必须继承自Illuminate\Database\Eloquent\Model。
# 定义一个 Eloquent 模型
class User extends Model {}
# 通过make:model命令来生成模型
php artisan make:model User
若没有特别指定,系统会默认自动对应名称为「类名称的小写复数形态」的数据库表。也可以在类中定义table属性自定义要对应的数据库表:
class User extends Model {
protected $table = 'my_users'; # 定义模型所映射的表
}
Eloquent也会假设每个数据库表都有一个字段名称为id的主键。您可以在类里定义 primaryKey 属性来重写。同样的,您也可以定义 connection 属性,指定模型连接到指定的数据库连接。
在默认情况下,在数据库表里需要有 updated_at 和 created_at 两个字段。如果不想设定或自动更新这两个字段,则将类里的 $timestamps 属性设为 false即可。
# 取出所有记录
$users = User::all();
# 根据主键取出一条数据
$user = User::find(1);
var_dump($user->name);
# 根据主键取出一条数据或抛出异常
$model = User::findOrFail(1);
$model = User::where('votes', '>', 100)->firstOrFail(); # 所有查询构造器里的方法,查询 Eloquent 模型时也可以使用。
# 处理ModelNotFoundException
要捕获ModelNotFoundException异常,需要往app/Exceptions/Handler.php文件中添加一些逻辑:
use Illuminate\Database\Eloquent\ModelNotFoundException;
class Handler extends ExceptionHandler {
public function render($request, Exception $e){
if ($e instanceof ModelNotFoundException){
// Custom logic for model not found...
}
return parent::render($request, $e);
}
}
# Eloquent 模型结合查询语法
$users = User::where('votes', '>', 100)->take(10)->get();
foreach ($users as $user){
var_dump($user->name);
}
# Eloquent 聚合查询
$count = User::where('votes', '>', 100)->count();
$users = User::whereRaw('age > ? and votes = 100', [25])->get();
# 拆分查询
User::chunk(200, function($users){
foreach ($users as $user){
//
}
});
# 指定查询时连接数据库
$user = User::on('connection-name')->find(1);
# 强制查询使用写入连接
$user = User::onWriteConnection()->find(1);
批量赋值
fillable 属性指定了哪些字段支持批量赋值,guarded与fillable相反。可以设定在类的属性里或是实例化后设定。
class User extends Model {
protected $fillable = ['first_name', 'last_name', 'email']; # 支持批量赋值的属性
protected $guarded = ['id', 'password']; # 不支持批量赋值的属性
}
protected $guarded = ['*']; # 阻挡所有属性被批量赋值
新增,更新,删除
# 储存新的模型数据,通常Eloquent模型主键值会自动递增。但是您若想自定义主键,将incrementing属性设成 false。
$user = new User;
$user->name = 'John';
$user->save();
$insertedId = $user->id; # 获取新增模型的ID
# 使用模型的Create方法,需要设定好 fillable 或 guarded 属性,因为 Eloquent 默认会防止批量赋值
$user = User::create(['name' => 'John']); # 在数据库中建立一个新的用户
$user = User::firstOrCreate(['name' => 'John']); # 以属性找用户,若没有则新增并取得新的实例
$user = User::firstOrNew(['name' => 'John']); # 以属性找用户,若没有则建立新的实例
# 更新取出的模型
$user = User::find(1);
$user->email = '[email protected]';
$user->save();
# 结合查询语句,批次更新模型
$affectedRows = User::where('votes', '>', 100)->update(['status' => 2]);
# 删除模型
$user = User::find(1);
$user->delete();
# 按主键值删除模型
User::destroy(1);
User::destroy([1, 2, 3]);
User::destroy(1, 2, 3);
# 只更新模型的时间戳
$user->touch();
软删除
通过软删除方式删除了一个模型后,模型中的数据并不是真的从数据库被移除。而是会设定 deleted_at时间戳。要让模型使用软删除功能,只要在模型类里加入 SoftDeletingTrait 即可:
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Model {
use SoftDeletes;
protected $dates = ['deleted_at'];
}
关联
# 定义一对一关联,一个User模型对应到一个Phone
class User extends Model {
public function phone(){
return $this->hasOne('App\Phone');
}
}
$phone = User::find(1)->phone; # 取得关联对象
# 自定义关联的键名
Eloquent假设对应的关联模型数据库表里,外键名称是基于模型名称。在这个例子里,默认 Phone 模型数据库表会以 user_id 作为外键。如果想要更改这个默认,可以传入第二个参数到 hasOne 方法里。更进一步,您可以传入第三个参数,指定关联的外键要对应到本身的哪个字段:
return $this->hasOne('App\Phone', 'foreign_key');
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
# 定义相对一对一关联
class Phone extends Model {
public function user(){
return $this->belongsTo('App\User', 'local_key', 'parent_key');
}
}
# 一对多,一篇Blog文章可能「有很多」评论
class Post extends Model {
public function comments(){
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
}
}
$comments = Post::find(1)->comments; # 取得所有评论
$comments = Post::find(1)->comments()->where('title', '=', 'foo')->first(); # 取得所有满足条件的评论
相对的关系使用belongsTo定义
# 多对多,一个用户(user)可能用有很多身份(role),而一种身份可能很多用户都有。多对多关联需要用到三个数据库表:users,roles,和role_user,role_user应该要有user_id和role_id字段。
class User extends Model {
public function roles(){
return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'foo_id'); # 第2个参数为枢纽表名
}
}
$roles = User::find(1)->roles; # 从 User 模型取得 roles
当然,也可以在Role模型同样使用belongsToMany来定义相对的关联。
# 远层一对多关联
假如有以下远层关联关系
countries id - integer name - string users id - integer country_id - integer name - string posts id - integer user_id - integer title - string
虽然posts数据库表本身没有country_id字段,但hasManyThrough方法让我们可以使用$country->posts取得country 的posts。
class Country extends Model {
public function posts(){
return $this->hasManyThrough('App\Post', 'App\User', 'country_id', 'user_id');
}
}
# 多态关联,用一个简单的关联方法,就让一个模型同时关联多个模型
staff id - integer name - string orders id - integer price - integer photos id - integer path - string imageable_id - integer imageable_type - string
class Photo extends Model {
public function imageable(){
return $this->morphTo();
}
}
class Staff extends Model {
public function photos(){
return $this->morphMany('App\Photo', 'imageable');
}
}
class Order extends Model {
public function photos(){
return $this->morphMany('App\Photo', 'imageable');
}
}
# 取得多态关联的对象
$staff = Staff::find(1);
foreach ($staff->photos as $photo){
//
}
# 取得多态关联对象的拥有者,Photo模型里的 imageable 关联会返回Staff或Order实例,取决于这是哪一种模型拥有的照片。
$photo = Photo::find(1);
$imageable = $photo->imageable;
关联查询
以关联模型作为查询限制
$posts = Post::has('comments')->get(); # 取得所有「至少有一篇评论」的Blog 文章
$posts = Post::has('comments', '>=', 3)->get(); # 可以指定运算符和数量
$posts = Post::has('comments.votes')->get(); # 获取嵌套的has声明
预载入
用来减少N +1查询问题
class Book extends Model {
public function author(){
return $this->belongsTo('App\Author');
}
}
# 不使用预载入
foreach (Book::all() as $book){
echo $book->author->name;
}
如果book的数量为N,将执行N+1次查询
# 使用预载入
foreach (Book::with('author')->get() as $book){
echo $book->author->name;
}
将只执行2次查询:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
# 也可以同时载入多种关联
$books = Book::with('author', 'publisher')->get();
# 预载入条件限制
$users = User::with(['posts' => function($query){
$query->where('title', 'like', '%first%');
}])->get();
# 延迟预载入
$books = Book::all();
$books->load('author', 'publisher');
# 对查询构建器进行条件限制
$books->load(['author' => function($query){
$query->orderBy('published_date', 'asc');
}]);
新增关联模型
# 附加一个关联模型
$comment = new Comment(['message' => 'A new comment.']);
$post = Post::find(1);
$comment = $post->comments()->save($comment);
新增的 comment 模型中 post_id 字段会被自动设定
# 同时新增很多关联模型
$comments = [
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another comment.']),
new Comment(['message' => 'The latest comment.'])
];
$post = Post::find(1);
$post->comments()->saveMany($comments);
# 从属关联模型
$account = Account::find(10);
$user->account()->associate($account);
$user->save();
集合
当查询返回的结果多于一条时,都会被转换成集合对象返回。
# 确认集合中里是否包含特定键值
$roles = User::find(1)->roles;
if ($roles->contains(2)){
//
}
# 集合也可以转换成数组或JSON
$roles = User::find(1)->roles->toArray();
$roles = User::find(1)->roles->toJson();
# 集合遍历
$roles = $user->roles->each(function($role){
//
});
# 集合过滤
$users = $users->filter(function($user){
return $user->isAdmin();
});
# 遍历传入集合里的每个对象到回调函数
$roles = User::find(1)->roles;
$roles->each(function($role){
//
});
# 依照属性值排序
$roles = $roles->sortBy('created_at');
$roles = $roles->sortByDesc('created_at');
$roles = $roles->sortBy(function($role){
return $role->created_at;
});
$roles = $roles->sortByDesc(function($role){
return $role->created_at;
});
# 获取器和修改器
class User extends Model {
# 获取器
public function getFirstNameAttribute($value) # 数据库中对应的字段名为first_name
{
return ucfirst($value);
}
# 修改器
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
}
属性类型转换
如果想要某些属性始终转换成另一个数据类型,可以在模型中增加 casts 属性。否则,您需要为每个属性定义修改器。
protected $casts = [
'is_admin' => 'boolean',
];
模型事件
Eloquent模型有很多事件可以触发,可以在模型操作的生命周期的不同时间点,使用下列方法绑定事件:
creating,created,updating,updated,saving,saved,deleting,deleted,restoring,restored。
如果creating、updating、saving、deleting事件返回false的话,就会取消数据库操作。
# 在EventServiceProvider中注册模型事件绑定
public function boot(DispatcherContract $events){
parent::boot($events);
User::creating(function($user){
if ( ! $user->isValid()) return false;
});
}
模型观察者
class UserObserver {
public function saving($model){ # 观察者类里要设定对应模型事件的方法
//
}
public function saved($model){
//
}
}
使用observe方法注册一个观察者实例:
User::observe(new UserObserver);
模型URL生成
当把一个模型实例传递给route或者 action 方法时,模型的主键会被插入实例占位符中:
Route::get('user/{user}', 'UserController@show');
action('UserController@show', [$user]);
如果想使用其他的属性而不是ID的话,可以覆盖模型的getRouteKey方法:
public function getRouteKey(){
return $this->slug;
}
转换成数组/ JSON
# 将模型数据转成数组
$user = User::with('roles')->first();
return $user->toArray();
# 也可以把整个的模型集合转换成数组
return User::all()->toArray();
# 将模型转换成 JSON
return User::find(1)->toJson();
# 从路由中返回模型
Route::get('users', function(){
return User::all(); # 当模型或集合被转换成字符串类型时会自动转换成 JSON 格式,所以可以直接返回对象
});
# 转换成数组或JSON时隐藏属性,只要在模型里增加hidden属性即可。此外,可以使用visible属性定义白名单
class User extends Model {
protected $hidden = ['password'];
protected $visible = ['first_name', 'last_name'];
}
# 增加不存在数据库字段的属性数据:只要定义一个获取器,再把对应的属性名称加到模型里的appends属性
public function getIsAdminAttribute(){
return $this->attributes['admin'] == 'yes';
}
protected $appends = ['is_admin'];
在appends数组中定义的值同样遵循模型中visible和hidden的设定。