Laravel 5文档阅读摘要

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中放置随机的tokenVerifyCsrfToken 中间件将保存在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'){  #  带默认值的参数

 

可以定义参数的全局模式,在RouteServiceProviderboot方法里定义模式:

$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

    });

 

});

上例中foobar为中间件键名。自定义的中间件的键名与类名映射关系需要在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,你可以选择注入符合给定IDUser类实体。在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将注入ID1User实体。若实体不存在,则抛出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);

 

响应宏

#  定义响应宏,通常定义在Providerboot方法内

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,或者直接在AppServiceProviderboot方法内添加:

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,并在Providerregister()方法中往系统容器注册实际工作类以及获取实际工作类实例的方法。然后再在应用配置中注册这个Provider。这样,应用初始化时会调用所有Providerregister()方法来间接注册获取实际工作类实例的方式。

 

定义一个基本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对象并调用对象的方法)

 

Laravelfacades和你建立的任何自定义facades,将会继承基类Facade,并只需要去实现一个方法:getFacadeAccessor()

 

Cache这个facade的调用:$value = Cache::get('key');

看一下类的实现:

class Cache extends Facade {

    protected static function getFacadeAccessor() { return 'cache'; }  #  该方法的作用就是返回服务容器绑定的名称

}

当用户在Cachefacade上执行任何的静态方法,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管理的库

3index.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);

}

}

4index.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

5HTTP核心对象中定义了一个bootstrappers数组,在http请求被执行前,会加载数组中定义的所有启动项,具体功能包括配置错误处理、日志记录、侦测应用程序环境等等;

6HTTP核心对象中定义了2HTTP中间件清单Illuminate\Foundation\Http\Kernel中是空数组,App\Http\Kernel类继承后在数组中加入了具体的项),$middleware数组定义全局的中间件,$routeMiddleware定义了特定路由的中间件。

7index.php中最终实际是调用了kernel的核心方法handle()handle()方法接收一个Request,返回一个Response

8handle方法中实际调用了路由分发等:$this->router->dispatch($request);

 

应用程序结构

Laravel 5全面采用新的自动加载标准PSR-4PSR-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 分布式配置文件提供了针对目录改变配置的方法,可以通过ApacheAllowOverride指令来设置是否启用

            /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_DEBUGAPP_KEYCACHE_DRIVERSESSION_DRIVER等等

Laravel通过DotEnvhttps://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

 

自定义

要自定义认证行为,需要修改AuthControllervalidator方法。

要自定义注册行为(比如添加其他字段),需要修改AuthControllercreate方法。

 

获取登录用户

$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来限制登录次数。它将根据唯一的用户名、emailip地址来进行限制:当数次登录尝试失败后,一分钟内将不能再进行登录操作。 

class AuthController extends Controller{

    use AuthenticatesAndRegistersUsers, ThrottlesLogins;

}

 

手工进行用户认证

class AuthController extends Controller{

    public function authenticate(){

        if (Auth::attempt(['email' => $email, 'password' => $password])) {  #  会查找指定email值的用户,若找到了,则再比较passwordhash值。

            // Authentication passed...

            return redirect()->intended('dashboard');

        }

    }

}

 

#  添加认证条件:active1

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();

});

 

#  结合rememberforever方法

$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中列出所有的eventlistener的映射关系,然后用以下命令来生成eventlistener类文件:

php artisan event:generate

已经存在的eventlistener不会被修改。

 

定义事件

一个事件类其实就是一个数据容器,携带一些和事件相关的数据,不包含逻辑内容。如:

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{

    //

}

 

如果想要操作底层任务队列的deleterelease方法,需要借助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);  #  删除目录

  

哈希

LaravelHash使用Bcrypt进行加密。

$password = Hash::make('secret');  #  加密

$password = bcrypt('secret');  #  直接使用函数加密

if (Hash::check('secret', $hashedPassword))  #  验证

if (Hash::needsRehash($hashed))  #  检查是否需要重新加密

 

辅助方法

Laravel扩展实现的一些方法:

数组方法

路径

字符串

URLs

杂项函数

 

本地化*

(略)

邮件*

(用到再看)

扩展包开发*

(开发插件,暂时用不到,略)

分页*

(实际应该会自己开发,用到再看)

队列*

(需要与各种队列驱动扩展包配合使用,用到再看)

会话

Laravel支持多种session后端驱动(文件、MemcachedRedis),并通过清楚、统一的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 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操作,同时使用另外的连接进行INSERTUPDATE、以及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 ByGroup ByHaving

$users = DB::table('users')

->orderBy('name', 'desc')

->groupBy('count')

->having('count', '>', 100)

   ->get();

 

#  OffsetLimit

$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();

 

结构生成器

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

 

迁移和数据填充

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

 

 

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 属性指定了哪些字段支持批量赋值guardedfillable相反。可以设定在类的属性里或是实例化后设定。

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),而一种身份可能很多用户都有。多对多关联需要用到三个数据库表usersroles,和role_userrole_user应该要有user_idrole_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 关联会返回StaffOrder实例,取决于这是哪一种模型拥有的照片。

$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模型有很多事件可以触发,可以在模型操作的生命周期的不同时间点,使用下列方法绑定事件:

creatingcreatedupdatingupdatedsavingsaveddeletingdeletedrestoringrestored

如果creatingupdatingsavingdeleting事件返回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数组中定义的值同样遵循模型中visiblehidden的设定。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(PHP,laravel,文档,阅读)