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路由

基本路由

定义针对不同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文件

HTTP请求

取得请求

两种方式,一是通过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

HTTP 响应

基本响应

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'

]);

Service Providers

每个自定义的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']);

});

}

}

Service Container

基本用法

在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

Contracts是所有Laravel主要组件实现所用的接口,可以看到Contracts目录下的目录结构和Illuminate中的一样。Contracts中为接口定义,Illuminate为具体实现。Illuminate中每个具体实现的类都扩展了其在Contracts中对应的接口。这样将接口和实现相分离,可以使依赖注入变得低耦合。

/laravel

/framework

/src

/Illuminate

/Auth

/Broadcasting

/Bus

...

/Contracts

/Auth

/Broadcasting

/Bus

...

Facades

基本用法

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>

Email

<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>

Email

<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>

Email

<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等。

Artisan Console

编写命令

自定义的命令都保存在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'

];

从代码中调用命令

可以从代码中调用命令,并传入参数。可以指定将命令添加到队列中执行。

此外,也可以从一个命令中调用另一个命令。

扩展框架*

Laravel Elixir*

加密

前置条件:确保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模板后缀名都要命名为.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@example.com'

],

[

'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' => 'john@example.com', 'votes' => 0]

);

#  添加自动递增ID的数据至数据表,如果数据表有自动递增的ID,可以使用insertGetId 添加数据并返回该 ID

$id = DB::table('users')->insertGetId(

['email' => 'john@example.com', 'votes' => 0]

);

#  添加多个数据进数据表

DB::table('users')->insert([

['email' => 'taylor@example.com', 'votes' => 0],

['email' => 'dayle@example.com', '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();

结构生成器

(提供一个与数据库无关的数据表产生方法)

迁移和数据填充

(和结构生成器一起对数据库进行版本控制)

Eloquent ORM

基本用法

模型通常放在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 = 'john@foo.com';

$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的设定。

Laravel 5文档阅读摘要相关推荐

  1. python asyncio文件操作_Python asyncio文档阅读摘要

    文档地址:https://docs.python.org/3/library/asyncio.html 文档第一句话说得很明白,asyncio是单线程并发,这种event loop架构是很多新型异步并 ...

  2. ExtJS4 API文档阅读(四)——Data

    2019独角兽企业重金招聘Python工程师标准>>> ExtJS4 API文档阅读(四)--Data 数据 Data包负责加载和保存你应用程序中的所有数据,由41个类构成,其中有三 ...

  3. Flink中GroupWindow和OverWindow各自的作用+window体系+文档阅读方式

    GroupWindow和OverWindow各自的作用 Flink Window 作用 完整实例 GroupWindow 对window中的数据按照字段进行分组 完整案例 OverWindow 在整个 ...

  4. TurboMail手机客户端—强大的附件文档阅读能力

    2019独角兽企业重金招聘Python工程师标准>>> 对于频繁使用邮件的用户而言,收发附件已是家常便饭,但对于手机查看附件,用户却遇到了很多问题.稍微低端的手机,除了txt格式的文 ...

  5. Qt文档阅读笔记-共享库的创建与调用

    使用共享库的符号 这个符号可以作用在变量.类.函数中,并且这些都可以被调用端使用. 在编译共享库中,需要使用export符号.在使用端调用的时候使用import符号. 这里是本人从文档中记录的笔记,大 ...

  6. Qt文档阅读笔记-加载HeightMap(高度图)构造3D地形图

    Qt文档阅读笔记-加载HeightMap(高度图)构造3D地形图 QHeightMapSurfaceDataProxy:是Q3DSurface的一个基本代理类. 他是专门加载高度图. 高度图是没有X, ...

  7. Qt文档阅读笔记-Rotations Example相关

    Rotations Example文档阅读笔记 使用这种方式,对y轴和z轴进行旋转. QQuaternion yRotation = QQuaternion::fromAxisAndAngle(0.0 ...

  8. 阅读器java_纯Java文档阅读器

    XDocViewer是一个纯Java的文档阅读器组件,可以方便的集成到Java应用中(也可以集成到Web应用中),它有如下特点: 1.免费 2.支持的文档格式丰富:MS Office文档(doc.do ...

  9. spring容器_Spring容器文档阅读要点记录

    Spring容器文档阅读要点记录 相关的库代码位于 org.springframework.beans 和 org.springframework.context包下面 容器的基本的接口 基本接口:B ...

最新文章

  1. 中科院微生物所高程组招聘助研3名(正式编制)
  2. 安装Xcode在Mac OS X10.7.3上
  3. 共享内存进程线程混合通信
  4. JVM源码分析之栈溢出完全解读
  5. 【Flink】Flink Heartbeat of TaskManager with id timed out.
  6. 百度地图KEY发布版SHA1和开发板SHA1如何获得
  7. 用DFS深度优先搜索求 1~n 的全排列
  8. 考研数学备考思路和计划制定
  9. MyBatis递归查询
  10. vue axios中文文档详解
  11. 训练集和验证集的划分
  12. NAACL2022-Prompt相关论文对Prompt的看法
  13. 2022年软件设计师考试复习资料(1)
  14. 怎么做精准引流?如何精准引流加粉?怎样引流被加精准粉?
  15. Java中map的含义
  16. 0736-1.6.1-如何配置CDSW使用本地的Pycharm
  17. Win32_PhysicalMemory 内存条 参数说明
  18. Bean重名报错 解决方案
  19. 网页抓取 - 完整指南
  20. 发布《Win32Asm与RadAsm开发教程》全视频系列的第一阶段。

热门文章

  1. ajax请求后新窗口打开,Ajax请求成功后打开新窗口地址
  2. java 反射调用_java反射调用方法
  3. 操作系统—存储器管理
  4. 一个轻巧高效的多线程c++stream风格异步日志(二)
  5. word显示打印机服务器脱机,打开word显示正在连接打印机是为什么
  6. 槛外人看NLP : 从古典诗词说到词语的色香味
  7. STC8H8K64U单片机-看门狗配置与讲解
  8. 云展网教程 | PDF上传后部分页面内容不显示/文字图片错位/PDF转换很慢或者失败
  9. 【Python基础第8期】任务1打卡
  10. 关于找不到mfc120u.dll文件错误与0xc000007b错误的解决方案