laravel版本安装
使用laragon 终端先cd到根目录
输入命令行:
composer global require "laravel/installer"
如果安装失败 尝试更换镜像
composer config -g repo.packagist composer https://packagist.laravel-china.org
安装成功后
输入命令行 新建一个名字作为laravel框架的文件名:laravel new 名字 (如:laravel new wiswork)
也可以输入命令
composer create-project --prefer-dist laravel/laravel wiswork(wiswork同上)
如果要下载安装 Laravel 其他版本应用,比如 5.6 版本,可以使用这个命令:
composer create-project --prefer-dist laravel/laravel wiswork 5.6.*。(wiswork为项目名字)
等待安装...
安装完毕后,定位到生成的项目下 比如cd wiswork
使用 PHP 内置的开发环境服务器为应用提供服务,
输入命令 php artisan serve
这样就可以在浏览器访问到框架的前端首页!
安装npm
npm(node package manager),通常称为Node包管理器,顾名思义,主要功能就是管理node包,包括:安装、卸载、更新等等。这里采用淘宝的镜像,相比国外的镜像,下载速度能更快一点
npm install -g cnpm --registry=https://registry.npm.taobao.org
安装vue-cli
npm install vue-cli -g
自动加载优化
composer install --optimize-autoloader
优化配置加载
php artisan config:cache
优化路由加载
php artisan route:cache
框架基础
基础组件-路由
我们以新建的wiswork应用为例,在 routes/web.php 中定义路由:
Route::get('hello',function (){
return 'Hello, welcome to 智世科技';
});
在浏览器中通过 http://wiswork.test/hello(我使用 Valet 作为开发环境,故而对应域名是 wiswork.test,实际域名以自己配置的为准)即可访问我们刚刚定义的路由,页面输出内容如下:Hello, welcome to 智世科技
get为请求方式 有get post any(类似于通用) put等
hello为页面的名,比如配一个路由为index 表示页面名为index 则把hello改为index即可
路由重定向
Route::redirect('/here','/there',301);
重定向:比如你在一个页面提交信息,提交完之后,要跳转到另外一个页面,而不是停留在原来页面,这时候只需要重定向到另外一个页面即可,不需要配置路由!
其中 here 表示原路由,there 表示重定向之后的路由,301 是一个 HTTP 状态码,用于标识重定向。
路由视图
接收一个 URI 作为第一个参数,以及一个视图名称作为第二个参数,此外,你还可以提供一个数组数据传递到该视图方法作为可选的第三个参数,该数组数据可用于视图中的数据渲染
Route::view('/welcome', 'welcome');
Route::view('/welcome', 'welcome', ['name' => '智世科技']);
数组中的值可渲染到模板页面中 如:{{$website}}
<div class="title m-b-md">
{{--智世科技--}}
{{$website}}
</div>
路由参数
有时我们需要在路由中获取 URI 请求参数。例如,如果要从 URL 中获取用户ID,需要通过如下方式定义路由参数
Route::get('user/{id}',function($id){
return 'User'.$id;
});
Route::get('user/{name?}', function ($name = null) {
return $name;
});
路由约束
路由参数在全局范围内被给定正则表达式约束,可以使用 pattern 方法。需要在 RouteServiceProvider 类的 boot 方法中定义这种约束模式
查找文件 RouteServiceProvider 的 boot()方法
public function boot()
{
Route::pattern('id', '[0-9]+');
parent::boot();
}
一旦模式被定义,将会自动应用到所有包含该参数名的路由中:
Route::get('user/{id}', function ($id) {
// 只有当 {id} 是数字时才会被调用
});
路由命名
Route::get('user/profile', 'UserController@showProfile')->name('profile');
命名之后,重定向的路由在控制器中就可以使用这个命名 profile
例如:return redirect->route('profile')
路由分组
Route::group(['prefix' => 'admin','namespace' => 'Admin'],function ()
{
Route::get('login', 'LoginController@showLogin')->name('admin.login');
Route::post('login', 'LoginController@login');
});
访问当前路由
// 获取当前路由实例
$route = Route::current();
// 获取当前路由名称
$name = Route::currentRouteName();
// 获取当前路由action属性
$action = Route::currentRouteAction();
控制器
使用 Artisan 命令快速创建一个控制器 (注意驼峰命名法)
php artisan make:controller UserController
所有的 Laravel 控制器应该继承自 Laravel 自带的控制器基类 App\Http\Controllers\Controller
class UserController extends Controller
{
//
}
CSRF保护
任何时候在 Laravel 应用中定义HTML表单,都需要在表单中引入CSRF令牌字段,这样CSRF保护中间件才能够正常验证请求。想要生成包含 CSRF 令牌的隐藏输入字段,可以使用辅助函数 csrf_field 来实现:
<form method="POST" action="/profile">
{{ csrf_field() }}
...
</form>
请求
要通过依赖注入获取当前 请求实例,需要在控制器的构造函数或方法中对 Illuminate\Http\Request 类进行类型提示,这样当前请求实例会被服务容器自动注入:
这样就可以在控制器的方法中传入参数
use Illuminate\Http\Request;
public function store(Request $request)
{
$name=$request->input('name');
}
如果你的控制器方法还期望获取路由参数,只需要将路由参数置于其它依赖之后即可,例如,如果你的路由定义如下:
Route::put('user/{id}','UserController@update');
定义完之后就可以使用在控制器的方法中引入需要访问的路由参数,
use Illuminate\Http\Request;
public function update(Request $request,$id)
{
//
}
获取所有请求输入值
使用 all 方法以数组格式获取所有输入值
$input = $request->all();
获取单个输入值
$name = $request->input('name');
也可以通过使用 Illuminate\Http\Request 实例上的动态属性来访问用户输入,如果你的应用表单包含 name 字段,那么可以像这样访问提交的值
$name = $request->name;
判断值是否在请求中出现,可以使用 has 方法,如果值出现过了且不为空,has 方法返回 true:
if ($request->has('name')) {
//
}
将输入存储到一次性 Session
Illuminate\Http\Request 实例的 flash 方法会将当前输入存放到一次性 Session(所谓的一次性指的是从 Session 中取出数据中,对应数据会从 Session 中销毁)中,这样在下一次请求时数据依然有效
$request-flash();
还可以使用 flashOnly 和 flashExcept 方法将输入数据子集存放到 Session 中,这些方法在session之外保存敏感信息时很有用:
$request->flashOnly('username', 'email');
$request->flashExcept('password');
输入存储到一次性 Session 然后重定向
使用 withInput 方法来将输入数据链接到 redirect 后面:
return redirect('form')->withInput();
return redirect('form')->withInput($request->except('password'));
取出上次请求数据,要从 Session 中取出上次请求的输入数据,可以使用 Request 实例的 old 方法
$username = $request->old('username');
如果你是在 Blade 模板中显示上次输入数据,使用辅助函数 old 更方便,如果给定参数没有对应输入,返回 null:
比如在表单提交的时候,某条信息输入错误,在系统提示错误的时候,页面刷新重新提交,原先填写无误的内容仍然显示,不需要再次输入
<input type="text" name="username" value="{{ old('username') }}">
从请求中获取cookie
使用 Illuminate\Http\Request 实例的 cookie 方法从请求中获取 cookie 的值:
$value = $request->cookie('name');
新增cookie到响应
将一个cookie附加到输出的 Illuminate\Http\Response 实例,你需要传递名称、值、以及cookie有效期(分钟)到这个方法:
return response('Hello World')->cookie(
'name', 'value', $minutes
);
生成cookie实例
生成一个 Symfony\Component\HttpFoundation\Cookie 实例以便后续附加到响应实例,可以使用一个全局的辅助函数cookie,这个cookie只有在附加到响应实例上才会发送到客户端:
$cookie = cookie('name', 'value', $minutes);
return response('Hello World')->cookie($cookie);
session
获取数据
通过Request实例来访问Session数据,可以在控制器方法中通过类型提示引入该实例,记住,控制器方法依赖通过Laravel服务容器自动注入:
public function showProfile(Request $request, $id)
{
$value = $request->session()->get('key');
}
获取所有session值
$data = $request->session()->all();
判断session是否存在如果值存在的话exists返回true
if ($request->session()->exists('users')) {
//
}
存储数据
获取到session实例后,就可以调用多个方法来与底层数据进行交互,例如,put方法存储新的数据到session中:
//通过put方法
$request->session()->put('key', 'value');
//通过全局辅助函数
session(['key' => 'value']);
推送数据到数组session
push 方法可用于推送数据到值为数组的session,例如,如果user.teams键包含团队名数组,可以像这样推送新值到该数组:
$request->session()->push('user.teams', 'developers');
获取并删除数据
pull方法将会从session获取并删除数据:
$value = $request->session()->pull('key', 'default');
验证
编写验证逻辑
validate方法接收一个HTTP请求输入数据和验证规则,如果验证规则通过,代码将会继续往下执行;
如果验证失败,将会抛出一个异常,相应的错误响应也会自动发送给用户。在一个传统的HTTP请求案例中,将会生成一个重定向响应,如果是AJAX请求则会返回一个JSON响应。
public function store(Request $request){
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// 验证通过,存储到数据库...
}
验证失败的话用户将会被重定向到控制器的create方法,从而允许我们在视图中显示错误信息:
@if (count($errors) > 0)
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
创建表单请求
通过Artisan命令生成请求验证文件:(文件命名必须规范-驼峰命名,并且具有可读性,从文件名就能看出是属于那个模块的验证请求文件!)
php artisan make:request StoreWisworkPost
生成的类位于app/Http/Requests目录下,添加验证规则到rules方法:
public function rules(){
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
然后在验证文件中,创建messages方法去编写自己的报错的信息(比如文字输入字数太多等)
public function messages(){
return [
'title.required' => '标题不能为空',
'title.unique:post'=>'请求方式错误',
'title.max'=>'最大值不能超过255',
'body.required'=>'字段不能为空'
];
}
编写验证规则之后,在控制期的方法中调用即可
public function store(StoreWisworkPost $request){
// The incoming request is valid...
}
如果不想使用ValidatesRequests trait提供的validate方法,可以使用Validator门面手动创建一个验证器实例,该门面上的make方法用于生成一个新的验证器实例:
use Validator;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect('post/create')
->withErrors($validator)
->withInput();
}
// 存储博客文章...
}
常用验证规则
alpha_num
该字段必须是字母或数字
array
该字段必须是PHP数组
exists:table,column
验证字段必须存在于指定数据表
'state' => 'exists:states'
numeric
验证字段必须是数值
required
输入字段值不能为空,以下情况字段值都为空:
string
验证字段必须是字符串
nullable
验证字段必须为null,这在验证一些可以为null的原生数据如整型或字符串时很有用。
更多可查看官方手册
用户认证
通过运行如下命令可快速生成认证所需要的路由和视图:
php artisan make:auth
该命令会生成注册和登录的后台视图,以及所有的认证路由,同时生成HomeController用于处理应用的登录请求.
php artisan make:auth 命令会在 resources/views/auth 目录下创建所有认证需要的视图。
make:auth 命令还创建了 resources/views/layouts 目录,该目录下包含了应用的基础布局文件。
这些视图都使用了 Bootstrap CSS 框架,你也可以根据需要对其进行自定义。
创建策略类
使用Artisan命令make:policy来生成一个policy(策略),生成的policy位于app/Policies目录下,如果这个目录之前不存在,Laravel会为我们生成:
php artisan make:policy PostPolicy
make:policy命令会生成一个空的policy类,如果你想要生成一个包含基本CRUD策略方法的policy类,在执行该命令的时候可以通过--model指定相应模型:
php artisan make:policy PostPolicy --model=Post
视图&模板--视图
创建视图
视图包含应用的 HTML 代码并将应用的控制器逻辑和表现逻辑进行分离。视图文件存放在 resources/views 目录。
<!-- 该视图存放 resources/views/hello1.php -->
<html>
<body>
<h1>Hello, {{ $name }}</h1>
</body>
</html>
由于这个视图存放在 resources/views/hello1.php,我们可以在全局的辅助函数 view 中这样返回它:
Route::get('/hello1', function () { return view('hello', ['name' => 'jahn']); });
传递给 view 方法的第一个参数是 resources/views 目录下相应的视图文件的名字,第二个参数是一个数组,该数组包含了在该视图中所有有效的数据。在这个例子中,我们传递了一个 name 变量,在视图中通过执行 echo 将其显示出来。
视图还可以嵌套在 resources/views 的子目录中,用“.”号来引用嵌套视图,比如,如果视图存放路径是resources/views/admin/profile.blade.php,那我们可以这样引用它:
return view('admin.profile', $data)
传递数据到视图
在上述例子中可以看到,我们可以简单通过数组方式将数据传递到视图
return view('hello', ['name' => 'Victoria
以这种方式传递数据的话,$data 应该是一个键值对数组,在视图中,就可以使用相应的键来访问数据值,比如 <?php echo $key; ?>。除此之外,还可以通过 with 方法添加独立的数据片段到视图:
$view = view('hello')->with('name', 'Victoria');
在视图间共享数据
有时候我们需要在所有视图之间共享数据片段,这时候可以使用视图工厂的 share 方法,通常,需要在服务提供者的boot 方法中调用 share 方法,你可以将其添加到 AppServiceProvider 或生成独立的服务提供者来存放它们:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* 在容器中注册绑定.
*
* @return void
* @author http://laravelacademy.org
*/
public function boot()
{
// 使用基于类的composers...
view()->composer(
'profile', 'App\Http\ViewComposers\ProfileComposer'
);
// 使用基于闭包的composers...
view()->composer('dashboard', function ($view) {});
}
/**
* 注册服务提供者.
*
* @return void
*/
public function register()
{
//
}
}
视图&模板--blade模板
简介
Blade 是 Laravel 提供的一个非常简单但很强大的模板引擎,不同于其他流行的 PHP 模板引擎,Blade 在视图中并不约束你使用 PHP 原生代码。所有的 Blade 视图都会被编译成原生 PHP 代码并缓存起来直到被修改,这意味着对应用的性能而言 Blade 基本上是零开销。Blade 视图文件使用 .blade.php 文件扩展并存放在 resources/views 目录下。
模板继承
使用 Blade 的两个最大优点是模板继承和切片,开始之前让我们先看一个例子。首先,我们检测一个“主”页面布局,由于大多数 Web 应用在不同页面中使用同一个布局,可以很方便的将这个布局定义为一个单独的 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>
正如你所看到的,该文件包含典型的 HTML 标记,然而,注意 @section 和 @yield 指令,前者正如其名字所暗示的,定义了一个内容的片段,而后者用于显示给定片段的内容。
扩展布局
定义子页面的时候,可以使用 Blade 的 @extends 指令来指定子页面所继承的布局,继承一个 Blade 布局的视图将会使用 @section 指令注入内容到布局的片段中,记住,如上面例子所示,这些片段的内容将会显示在布局中使用@yield 的地方:
<!-- 存放在 resources/views/child.blade.php -->
@extends('layouts.master')
@section('title', 'Page Title')
@section('sidebar')
@parent
<p>This is appended to the master sidebar.</p>
@endsection
@section('content')
<p>This is my body content.</p>
@endsection
在本例中,sidebar 片段使用 @parent 指令来追加(而非覆盖)内容到布局中 sidebar,@parent 指令在视图渲染时将会被布局中的内容替换。
当然,和原生 PHP 视图一样,Blade 视图可以通过 view 方法直接从路由中返回:
Route::get('blade', function () {
return view('child');
});
数据显示
可以通过两个花括号包裹变量来显示传递到视图的数据,比如,如果给出如下路由:
Route::get('greeting', function () {
return view('welcome', ['name' => 'Samantha']);
});
那么可以通过如下方式显示 name 变量的内容:
Hello, {{ $name }}.
当然,不限制显示到视图中的变量内容,你还可以输出任何 PHP 函数,实际上,可以将任何 PHP 代码放到 Blade 模板语句中:
The current UNIX timestamp is {{ time() }}.
输出存在的数据
有时候你想要输出一个变量,但是不确定该变量是否被设置,我们可以通过如下 PHP 代码:
{{ isset($name) ? $name : 'Default' }}
在本例中,如果 $name 变量存在,其值将会显示,否则将会显示“Default”。
显示原生数据
默认情况下,Blade 的 {{ }} 语句已经通过 PHP 的 htmlentities 函数处理以避免 XSS 攻击,如果你不想要数据被处理,可以使用如下语法:
Hello, {!! $name !!}.
注:输出用户提供的内容时要当心,对用户提供的内容总是要使用双花括号包裹以避免直接输出 HTML 代码
Blade & JavaScript 框架
由于很多 JavaScript 框架也是用花括号来表示要显示在浏览器中的表达式,可以使用 @ 符号来告诉 Blade 渲染引擎该表达式应该保持原生格式不作改动。比如:
<h1>Laravel</h1>
Hello, @{{ name }}.
在本例中,@ 符将会被 Blade 移除,但是,{{ name }} 表达式将会保持不变,避免被 JavaScript 框架渲染。
@verbatim指令
如果你在模板中很大一部分显示JavaScript变量,那么可以将这部分HTML封装在@verbatim指令中,这样就不需要在每个Blade输出表达式前加上@前缀:
@verbatim
<div class="container">
Hello, {{ name }}.
</div>
@endverbatim
流程控制
除了模板继承和数据显示之外,Blade 还为常用的 PHP 流程控制提供了便利操作,比如条件语句和循环,这些快捷操作提供了一个干净、简单的方式来处理 PHP 的流程控制,同时保持和 PHP 相应语句的相似。
If 语句
可以使用 @if , @elseif , @else 和 @endif 来构造 if 语句,这些指令函数和 PHP 的相同:
@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif
为方便起见,Blade 还提供了 @unless 指令:
@unless (Auth::check())
You are not signed in.
@endunless
循环
除了条件语句,Blade 还提供了简单指令处理 PHP 支持的循环结构,同样,这些指令函数和 PHP 的一样:
@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
在循环的时候可以使用$loop变量获取循环信息,例如是否是循环的第一个或最后一个迭代:
@foreach ($users as $user)
@if ($user->type == 1)
@continue
@endif
<li>{{ $user->name }}</li>
@if ($user->number == 5)
@break
@endif
@endforeach
还可以使用指令声明来引入条件:
@foreach ($users as $user)
@continue($user->type == 1)
<li>{{ $user->name }}</li>
@break($user->number == 5)
@endforeach
$loop变量
在循环的时候,可以在循环体中使用$loop变量,该变量提供了一些有用的信息,比如当前循环索引,以及当前循环是不是第一个或最后一个迭代:
@foreach ($users as $user)
@if ($loop->first)
This is the first iteration.
@endif
@if ($loop->last)
This is the last iteration.
@endif
<p>This is user {{ $user->id }}</p>
@endforeach
如果你身处嵌套循环,可以通过 $loop 变量的 parent 属性访问父级循环:
@foreach ($users as $user)
@foreach ($user->posts as $post)
@if ($loop->parent->first)
This is first iteration of the parent loop.
@endif
@endforeach
@endforeach
属性 |
描述 |
$loop->index |
当前循环迭代索引 (从0开始). |
$loop->iteration |
当前循环迭代 (从1开始). |
$loop->remaining |
当前循环剩余的迭代 |
$loop->count |
迭代数组元素的总数量 |
$loop->first |
是否是当前循环的第一个迭代 |
$loop->last |
是否是当前循环的最后一个迭代 |
$loop->depth |
当前循环的嵌套层级 |
$loop->parent |
嵌套循环中的父级循环变量 |
包含子视图
Blade 的 @include 指令允许你很简单的在一个视图中包含另一个 Blade 视图,所有父级视图中变量在被包含的子视图中依然有效:
<div>
@include('shared.errors')
<form>
<!-- Form Contents -->
</form>
</div>
尽管被包含的视图继承所有父视图中的数据,你还可以传递额外参数到被包含的视图:
@include('view.name', ['some' => 'data'])
注:不要在 Blade 视图中使用 __DIR__ 和 __FILE__ 常量,因为它们会指向缓存视图的路径。
为集合渲染视图
你可以使用 Blade 的 @each 指令通过一行代码循环引入多个局部视图:
@each('view.name', $jobs, 'job')
该指令的第一个参数是数组或集合中每个元素要渲染的局部视图,第二个参数是你希望迭代的数组或集合,第三个参数是要分配给当前视图的变量名。举个例子,如果你要迭代一个 jobs 数组,通常你需要在局部视图中访问 $job 变量。在局部视图中可以通过 key 变量访问当前迭代的键。
你还可以传递第四个参数到 @each 指令,该参数用于指定给定数组为空时渲染的视图:
@each('view.name', $jobs, 'job', 'view.empty')
javaScript & css --起步
简介
Laravel并不强制你使用什么JavaScript或者CSS预处理器,不过也确实提供了对很多应用都很有用的Bootstrap和Vue的一些基本使用。默认情况下,Laravel使用NPM来安装这些前端包。
编写CSS
Laravel应用根目录下的package.json文件包含了bootstrap-sass扩展包以便我们使用Bootstrap构建前端原型,不过,你也可以按照自己应用的需要来增删package.json文件中的扩展包。此外,并不是必须要使用Bootstrap框架来构建Laravel应用——这只是为选择使用Bootstrap的开发者提供一个好的起点。
编译CSS之前,使用NPM安装应用的前端依赖:
npm install
使用npm install安装好前端依赖之后,可以使用Gulp编译SASS文件为原生的CSS,gulp命令会处理gulpfile.js文件中的声明。通常,编译好的CSS文件会被放置到public/css目录下:
gulp
Laravel自带的默认gulpfile.js文件会编译SASS文件resources/assets/sass/app.scss,这个app.scss文件将会导入一个包含SASS变量的文件并加载Bootstrap,从而助力我们快速在应用中引入Bootstrap资源,你也可以按照自己的需要自定义app.scss文件,甚至可以通过配置Laravel Elixir使用一个完全不同的预处理器。
编写JavaScript
应用所需要的所有JavaScript依赖都可以在应用根目录下的package.json中找到,这个文件和composer.json类似,只不过它指定的是JavaScript依赖而不是PHP依赖。你可以使用NPM来安装这些依赖:
npm install
默认情况下,Laravel自带的package.json文件引入了一些扩展包,比如vue和vue-resource,以便你快速构建JavaScript应用,同样,你可以按照应用的需要随意增删package.json中的扩展包。
扩展包安装好之后,可以使用gulp命令来编译前端资源,Gulp是一个JavaScript命令行构建工具,当你执行gulp命令的时候,Gulp将会执行gulpfile.js中的声明
gulp
默认情况下,Laravel自带的gulpfile.js将会编译SASS和resources/assets/js/app.js文件,在app.js文件中你可以注册Vue组件,或者你倾向于其它JavaScript框架,配置你自己的JavaScript应用。你所编译的JavaScript文件通常会存放在public/js目录下。
编写Vue组件
默认情况下,新安装的Laravel应用将会在resources/assets/js/components目录下包含一个Vue组件Example.vue,这个Vue组件是一个单文件Vue组件示例,其中定义了相关的JavaScript和HTML模板,单文件组件为构建JavaScript驱动的应用提供了便利。这个示例组件在app.js中注册:
Vue.component('example', require('./components/Example.vue'));
要在应用中使用这个组件,只需要将其丢到某个HTML模板中。例如,在运行完Artisan命令make:auth创建登录和注册视图之后,就可以将这个组件丢到Blade模板home.blade.php中:
@extends('layouts.app')
@section('content')
<example></example>
@endsection
Eloquent ORM
简介
Laravel 自带的 Eloquent ORM 提供了一个美观、简单的与数据库打交道的 ActiveRecord 实现,每张数据表都对应一个与该表进行交互的“模型”,模型允许你在表中进行数据查询,以及插入、更新、删除等操作。
开始之前,确保在config/database.php文件中配置好了数据库连接。
定义模型
作为开始,让我们创建一个 Eloquent 模型,模型通常位于app目录下,你也可以将其放在其他可以被composer.json文件自动加载的地方。所有Eloquent模型都继承自 Illuminate\Database\Eloquent\Model类。
创建模型实例最简单的办法就是使用 Artisan 命令make:model:
php artisan make:model User
如果你想要在生成模型时生成数据库迁移,可以使用--migration或-m选项:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
//
}
表名
注意我们并没有告诉 Eloquent 我们的Flight模型使用哪张表。默认规则是模型类名的复数作为与其对应的表名,除非在模型类中明确指定了其它名称。所以,在本例中,Eloquent 认为Flight模型存储记录在flights表中。你也可以在模型中定义table属性来指定自定义的表名
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 关联到模型的数据表
*
* @var string
*/
protected $table = 'my_flights';
}
主键
Eloquent 默认每张表的主键名为id,你可以在模型类中定义一个$primaryKey属性来覆盖该约定。
此外,Eloquent默认主键字段是自增的整型数据,这意味着主键将会被自动转化为int类型,如果你想要使用非自增或非数字类型主键,必须在对应模型中设置$incrementing属性为false。
时间戳
默认情况下,Eloquent 期望created_at和updated_at已经存在于数据表中,如果你不想要这些 Laravel 自动管理的列,在模型类中设置$timestamps属性为false:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 表明模型是否应该被打上时间戳
*
* @var bool
*/
public $timestamps = false;
}
如果你需要自定义时间戳格式,设置模型中的$dateFormat属性。该属性决定日期被如何存储到数据库中,以及模型被序列化为数组或 JSON 时日期的格式:
数据库连接
默认情况下,所有的 Eloquent 模型使用应用配置中的默认数据库连接,如果你想要为模型指定不同的连接,可以通过$connection 属性来设置:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* The connection name for the model.
*
* @var string
*/
protected $connection = 'connection-name';
}
获取模型
创建完模型及其关联的数据表后,就要准备从数据库中获取数据。将Eloquent模型看作功能强大的查询构建器,你可以使用它来流畅的查询与其关联的数据表。例如:
<?php
use App\Flight;
$flights = App\Flight::all();
foreach ($flights as $flight) {
echo $flight->name;
}
添加额外约束
Eloquent 的all方法返回模型表的所有结果,由于每一个Eloquent模型都是一个查询构建器,你还可以添加约束条件到查询,然后使用get方法获取对应结果:
$flights = App\Flight::where('active', 1)
->orderBy('name', 'desc')
->take(10)
->get();
注意:由于 Eloquent 模型本质上就是查询构建器,你可以在Eloquent查询中使用查询构建器的所有方法。
集合
对 Eloquent 中获取多个结果的方法(比如all和get)而言,其返回值是Illuminate\Database\Eloquent\Collection的一个实例,Collection类提供了多个有用的函数来处理Eloquent结果集:
$flights = $flights->reject(function ($flight) {
return $flight->cancelled;
});
当然,你也可以像数组一样循环遍历该集合:
foreach ($flights as $flight) {
echo $flight->name;
}
组块结果集
如果你需要处理成千上万个 Eloquent 结果,可以使用chunk命令。chunk方法会获取一个“组块”的 Eloquent 模型,并将其填充到给定闭包进行处理。使用chunk方法能够在处理大量数据集合时有效减少内存消耗:
Flight::chunk(200, function ($flights) {
foreach ($flights as $flight) {
//
}
});
使用游标
cursor方法允许你使用游标迭代处理数据库记录,一次只执行单个查询,在处理大批量数据时,cursor方法可大幅减少内存消耗:
foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
//
}
获取单个模型/聚合
当然,除了从给定表中获取所有记录之外,还可以使用find和first获取单个记录。这些方法返回单个模型实例而不是返回模型集合:
// 通过主键获取模型...
$flight = App\Flight::find(1);
// 获取匹配查询条件的第一个模型...
$flight = App\Flight::where('active', 1)->first();
还可以通过传递主键数组来调用find方法,这将会返回匹配记录集合:
$flights = App\Flight::find([1, 2, 3]);
Not Found 异常
有时候你可能想要在模型找不到的时候抛出异常,这在路由或控制器中非常有用,findOrFail和firstOrFail方法会获取查询到的第一个结果。然而,如果没有任何查询结果,Illuminate\Database\Eloquent\ModelNotFoundException异常将会被抛出:
$model = App\Flight::findOrFail(1);
$model = App\Flight::where('legs', '>', 100)->firstOrFail();
如果异常没有被捕获,那么HTTP 404 响应将会被发送给用户,所以在使用这些方法的时候没有必要对返回404响应编写明确的检查:
Route::get('/api/flights/{id}', function ($id) {
return App\Flight::findOrFail($id);
});
获取聚合
当然,你还可以使用查询构建器聚合方法,例如count、sum、max,以及其它查询构建器提供的聚合方法。这些方法返回计算后的结果而不是整个模型实例:
$count = App\Flight::where('active', 1)->count();
$max = App\Flight::where('active', 1)->max('price');
插入/更新模型
插入
想要在数据库中插入新的记录,只需创建一个新的模型实例,设置模型的属性,然后调用save方法:
<?php
namespace App\Http\Controllers;
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class FlightController extends Controller{
/**
* 创建一个新的航班实例
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
// Validate the request...
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
}
在这个例子中,我们只是简单分配HTTP请求中的name参数值给App\Flight模型实例的那么属性,当我们调用save方法时,一条记录将会被插入数据库。created_at和updated_at时间戳在save方法被调用时会自动被设置,所以没必要手动设置它们。
更新
save方法还可以用于更新数据库中已存在的模型。要更新一个模型,应该先获取它,设置你想要更新的属性,然后调用save方法。同样,updated_at时间戳会被自动更新,所以没必要手动设置其值:
$flight = App\Flight::find(1);
$flight->name = 'New Flight Name';
$flight->save();
更新操作还可以同时修改给定查询提供的多个模型实例,在本例中,所有有效且destination=San Diego的航班都被标记为延迟:
App\Flight::where('active', 1)
->where('destination', 'San Diego')
->update(['delayed' => 1]);
update方法要求以数组形式传递键值对参数,代表着数据表中应该被更新的列。
批量赋值
还可以使用create方法保存一个新的模型。该方法返回被插入的模型实例。但是,在此之前,你需要指定模型的fillable或guarded属性,因为所有Eloquent模型都通过批量赋值(Mass Assignment)进行保护。
当用户通过 HTTP 请求传递一个不被期望的参数值时就会出现安全隐患,然后该参数以不被期望的方式修改数据库中的列值。例如,恶意用户通过 HTTP 请求发送一个is_admin参数,然后该参数映射到模型的create方法,从而允许用户将自己变成管理员。
所以,你应该在模型中定义哪些属性是可以进行赋值的,使用模型上的$fillable属性即可实现。例如,我们设置Flight模型上的name属性可以被赋值:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 可以被批量赋值的属性.
*
* @var array
*/
protected $fillable = ['name'];
}
设置完可以被赋值的属性之后,我们就可以使用create方法在数据库中插入一条新的记录。create方法返回保存后的模型实例:
$flight = App\Flight::create(['name' => 'Flight 10']);
删除模型
要删除一个模型,调用模型实例上的delete方法:
$flight = App\Flight::find(1);
$flight->delete();
通过主键删除模型
在上面的例子中,我们在调用delete方法之前从数据库中获取该模型,然而,如果你知道模型的主键的话,可以调用destroy方法直接删除而不需要获取它:
App\Flight::destroy(1);
App\Flight::destroy([1, 2, 3]);
App\Flight::destroy(1, 2, 3);
软删除
除了从数据库删除记录外,Eloquent还可以对模型进行“软删除”。当模型被软删除后,它们并没有真的从数据库删除,而是在模型上设置一个deleted_at属性并插入数据库,如果模型有一个非空deleted_at值,那么该模型已经被软删除了。要启用模型的软删除功能,可以使用模型上的Illuminate\Database\Eloquent\SoftDeletestrait并添加deleted_at列到$dates属性:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Flight extends Model{
use SoftDeletes;
/**
* 应该被调整为日期的属性
*
* @var array
*/
protected $dates = ['deleted_at'];
}
当然,应该添加deleted_at列到数据表。Laravel Schema构建器包含一个帮助函数来创建该列:
Schema::table('flights', function ($table) {
$table->softDeletes();
});
现在,当调用模型的delete方法时,deleted_at列将被设置为当前日期和时间,并且,当查询一个使用软删除的模型时,被软删除的模型将会自动从查询结果中排除。
判断给定模型实例是否被软删除,可以使用trashed方法:
if ($flight->trashed()) {
//
}
事件
Eloquent模型可以触发事件,允许你在模型生命周期中的多个时间点调用如下这些方法:creating, created, updating, updated, saving, saved,deleting, deleted, restoring, restored。事件允许你在一个指定模型类每次保存或更新的时候执行代码。
基本使用
一个新模型被首次保存的时候,creating和created事件会被触发。如果一个模型已经在数据库中存在并调用save方法,updating/updated事件会被触发,无论是创建还是更新,saving/saved事件都会被调用。
举个例子,我们在服务提供者中定义一个Eloquent事件监听器,在事件监听器中,我们会调用给定模型的isValid方法,如果模型无效会返回false。如果从Eloquent事件监听器中返回false则取消save/update操作:
<?php
namespace App\Providers;
use App\User;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider{
/**
* 启动所有应用服务
*
* @return void
*/
public function boot()
{
User::creating(function ($user) {
return $user->isValid();
});
}
/**
* 注册服务提供者.
*
* @return void
*/
public function register()
{
//
}
}
Eloquent ORM —— 关联关系
简介
数据表经常要与其它表做关联,比如一篇博客文章可能有很多评论,或者一个订单会被关联到下单用户,Eloquent 使得组织和处理这些关联关系变得简单,并且支持多种不同类型的关联关系:
定义关联关系
Eloquent 关联关系以Eloquent模型类方法的形式被定义。和 Eloquent 模型本身一样,关联关系也是强大的查询构建器,定义关联关系为函数能够提供功能强大的方法链和查询能力。例如:
$user->posts()->where('active', 1)->get();
但是,在深入使用关联关系之前,让我们先学习如何定义每种关联类型:
一对一关联是一个非常简单的关联关系,例如,一个User模型有一个与之对应的Phone模型。要定义这种模型,我们需要将phone方法置于User模型中,phone方法会调用 Eloquent 模型基类上hasOne方法并返回其结果:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 获取关联到用户的手机
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
传递给hasOne方法的第一个参数是关联模型的名称,关联关系被定义后,我们可以使用 Eloquent 的动态属性获取关联记录。动态属性允许我们访问关联函数就像它们是定义在模型上的属性一样:
$phone = User::find(1)->phone;
Eloquent 默认关联关系的外键基于模型名称,在本例中,Phone模型默认有一个user_id外键,如果你希望重写这种约定,可以传递第二个参数到hasOne方法:
return $this->hasOne('App\Phone', 'foreign_key');
此外,Eloquent 假设外键应该在父级上有一个与之匹配的id,换句话说,Eloquent 将会通过user表的id值去phone表中查询user_id与之匹配的Phone记录。如果你想要关联关系使用其他值而不是id,可以传递第三个参数到hasOne来指定自定义的主键:
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
定义相对的关联
我们可以从User中访问Phone模型,相应的,我们也可以在Phone模型中定义关联关系从而让我们可以拥有该phone的User。我们可以使用belongsTo方法定义与hasOne关联关系相对的关联:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model{
/**
* 获取手机对应的用户
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
在上面的例子中,Eloquent 将会尝试通过Phone模型的user_id去User模型查找与之匹配的记录。Eloquent 通过关联关系方法名并在方法名后加_id后缀来生成默认的外键名。然而,如果Phone模型上的外键不是user_id,也可以将自定义的键名作为第二个参数传递到belongsTo方法:
/**
* 获取手机对应的用户
*/
public function user(){
return $this->belongsTo('App\User', 'foreign_key');
}
如果父模型不使用id作为主键,或者你希望使用别的列来连接子模型,可以将父表自定义键作为第三个参数传递给belongsTo方法:
/**
* 获取手机对应的用户
*/
public function user(){
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}
一对多
“一对多”是用于定义单个模型拥有多个其它模型的关联关系。例如,一篇博客文章拥有无数评论,和其他关联关系一样,一对多关联通过在 Eloquent 模型中定义方法来定义:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model{
/**
* 获取博客文章的评论
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
}
记住,Eloquent 会自动判断Comment模型的外键,为方便起见,Eloquent 将拥有者模型名称加上_id后缀作为外键。因此,在本例中,Eloquent 假设Comment模型上的外键是post_id。
关联关系被定义后,我们就可以通过访问comments属性来访问评论集合。记住,由于 Eloquent 提供“动态属性”,我们可以像访问模型的属性一样访问关联方法:
$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
//
}
当然,由于所有关联同时也是查询构建器,我们可以添加更多的条件约束到通过调用comments方法获取到的评论上:
$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();
和hasOne方法一样,你还可以通过传递额外参数到hasMany方法来重新设置外键和本地主键:
return $this->hasMany('App\Comment', 'foreign_key');
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
一对多(逆向)
现在我们可以访问文章的所有评论了,接下来让我们定义一个关联关系允许通过评论访问所属文章。要定义与hasMany相对的关联关系,需要在子模型中定义一个关联方法去调用belongsTo方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model{
/**
* 获取评论对应的博客文章
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
关联关系定义好之后,我们可以通过访问动态属性post来获取一条Comment对应的Post:
$comment = App\Comment::find(1);
echo $comment->post->title;
在上面这个例子中,Eloquent 尝试匹配Comment模型的post_id与Post模型的id,Eloquent 通过关联方法名加上_id后缀生成默认外键,当然,你也可以通过传递自定义外键名作为第二个参数传递到belongsTo方法,如果你的外键不是post_id,或者你想自定义的话:
/**
* 获取评论对应的博客文章
*/
public function post(){
return $this->belongsTo('App\Post', 'foreign_key');
}
如果你的父模型不使用id作为主键,或者你希望通过其他列来连接子模型,可以将自定义键名作为第三个参数传递给belongsTo方法:
/**
* 获取评论对应的博客文章
*/
public function post(){
return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
多对多
多对多关系比hasOne和hasMany关联关系要稍微复杂一些。这种关联关系的一个例子就是一个用户有多个角色,同时一个角色被多个用户共用。例如,很多用户可能都有一个“Admin”角色。要定义这样的关联关系,需要三个数据表:users、roles和role_user,role_user表按照关联模型名的字母顺序命名,并且包含user_id和role_id两个列。
多对多关联通过编写一个调用 Eloquent 基类上的belongsToMany方法的函数来定义,例如,我们在User模型上定义roles方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 用户角色
*/
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
关联关系被定义之后,可以使用动态属性roles来访问用户的角色:
$user = App\User::find(1);
foreach ($user->roles as $role) {
//
}
当然,和所有其它关联关系类型一样,你可以调用roles方法来添加条件约束到关联查询上:
$roles = App\User::find(1)->roles()->orderBy('name')->get();
正如前面所提到的,为了决定关联关系连接表的表名,Eloquent以字母顺序连接两个关联模型的名字。然而,你可以重写这种约定——通过传递第二个参数到belongsToMany方法:
return $this->belongsToMany('App\Role', 'user_roles');
除了自定义连接表的表名,你还可以通过传递额外参数到belongsToMany方法来自定义该表中字段的列名。第三个参数是你定义的关系模型的外键名称,第四个参数你要连接到的模型的外键名称:
return $this->belongsToMany('App\Role', 'user_roles', 'user_id', 'role_id');
定义相对的关联关系
要定义与多对多关联相对的关联关系,只需在关联模型中在调用一下belongsToMany方法即可。让我们在Role模型中定义users方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model{
/**
* 角色用户
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
正如你所看到的,定义的关联关系和与其对应的User中定义的一模一样,只是前者引用App\Role,后者引用App\User,由于我们再次使用了belongsToMany方法,所有的常用表和键自定义选项在定义与多对多相对的关联关系时都是可用的。
获取中间表字段
正如你已经学习到的,处理多对多关联要求一个中间表。Eloquent 提供了一些有用的方法来与其进行交互,例如,我们假设User对象有很多与之关联的Role对象,访问这些关联关系之后,我们可以使用模型上的pivot属性访问中间表:
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
注意我们获取到的每一个Role模型都被自动赋上了pivot属性。该属性包含一个代表中间表的模型,并且可以像其它 Eloquent 模型一样使用。
默认情况下,只有模型键才能用在pivot对象上,如果你的pivot表包含额外的属性,必须在定义关联关系时进行指定:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
如果你想要你的pivot表自动包含created_at和updated_at时间戳,在关联关系定义时使用withTimestamps方法:
return $this->belongsToMany('App\Role')->withTimestamps()
通过中间表字段过滤关联关系
你还可以在定义关联关系的时候使用wherePivot和wherePivotIn方法过滤belongsToMany返回的结果集:
return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);
远层的一对多
远层一对多”关联为通过中间关联访问远层的关联关系提供了一个便利之道。例如,Country模型通过中间的User模型可能拥有多个Post模型。在这个例子中,你可以轻易的聚合给定国家的所有文章,让我们看看定义这个关联关系需要哪些表:
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来访问一个国家的所有文章。要执行该查询,Eloquent 在中间表$users上检查country_id,查找到相匹配的用户ID后,通过用户ID来查询posts表。
既然我们已经查看了该关联关系的数据表结构,接下来让我们在Country模型上进行定义:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Country extends Model{
/**
* 获取指定国家的所有文章
*/
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User');
}
}
第一个传递到hasManyThrough方法的参数是最终我们希望访问的模型的名称,第二个参数是中间模型名称。
当执行这种关联查询时通常 Eloquent 外键规则会被使用,如果你想要自定义该关联关系的外键,可以将它们作为第三个、第四个参数传递给hasManyThrough方法。第三个参数是中间模型的外键名,第四个参数是最终模型的外键名。
class Country extends Model{
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User', 'country_id', 'user_id');
}
}
多态关联
表结构
多态关联允许一个模型在单个关联下属于多个不同模型。例如,假设应用用户既可以对文章进行评论也可以对视频进行评论,使用多态关联,你可以在这两种场景下使用单个comments表,首先,让我们看看构建这种关联关系需要的表结构:
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
两个重要的需要注意的列是 comments 表上的 commentable_id 和 commentable_type。commentable_id列对应 Post 或Video 的 ID 值,而 commentable_type 列对应所属模型的类名。当访问 commentable 关联时,ORM 根据commentable_type 字段来判断所属模型的类型并返回相应模型实例。
模型结构
接下来,让我们看看构建这种关联关系需要在模型中定义什么:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* Get all of the owning commentable models.
*/
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
/**
* Get all of the post's comments.
*/
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}
class Video extends Model
{
/**
* Get all of the video's comments.
*/
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}
获取多态关联
数据表和模型定义好以后,可以通过模型访问关联关系。例如,要访问一篇文章的所有评论,可以通过使用动态属性comments :
$post = App\Post::find(1);
foreach ($post->comments as $comment) {
//
}
你还可以通过调用 morphTo 方法从多态模型中获取多态关联的所属对象。在本例中,就是 Comment 模型中的commentable 方法。因此,我们可以用动态属性的方式访问该方法:
$comment = App\Comment::find(1);
$commentable = $comment->commentable;
Comment 模型的 commentable 关联返回 Post 或 Video 实例,这取决于哪个类型的模型拥有该评论。
自定义多态类型
默认情况下,Laravel使用完全限定类名来存储关联模型的类型。举个例子,上面示例中的Comment可能属于某个Post或Video,默认的commentable_type可能是App\Post或App\Video。不过,有时候你可能需要解除数据库和应用内部结构之间的耦合,这样的情况下,可以定义一个morphMap关联来告知Eloquent为每个模型使用自定义名称替代完整类名:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'posts' => App\Post::class,
'videos' => App\Video::class,
]);
你可以在AppServiceProvider的boot方法中注册这个morphMap,如果需要的话,也可以创建一个独立的服务提供者来实现这一功能。
多对多的多态关联
表结构
除了传统的多态关联,还可以定义“多对多”的多态关联,例如,一个博客的 Post 和 Video 模型可能共享一个 Tag模型的多态关联。使用对多对的多态关联允许你在博客文章和视频之间有唯一的标签列表。首先,让我们看看表结构:
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
模型结构
接下来,我们准备在模型中定义该关联关系。Post 和 Video 模型都有一个 tags 方法调用 Eloquent 基类的morphToMany 方法:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model{
/**
* 获取指定文章所有标签
*/
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
}
定义相对的关联关系
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model{
/**
* 获取所有分配该标签的文章
*/
public function posts()
{
return $this->morphedByMany('App\Post', 'taggable');
}
/**
* 获取分配该标签的所有视频
*/
public function videos()
{
return $this->morphedByMany('App\Video', 'taggable');
}
}
获取关联关系
定义好数据库和模型后可以通过模型访问关联关系。例如,要访问一篇文章的所有标签,可以使用动态属性tags:
$post = App\Post::find(1);
foreach ($post->tags as $tag) {
//
}
还可以通过访问调用morphedByMany的方法名从多态模型中获取多态关联的所属对象。在本例中,就是Tag模型中的posts或者videos方法:
$tag = App\Tag::find(1);
foreach ($tag->videos as $video) {
//
}
Eloquent ORM —— 序列化
简介
当构建 JSON API 时,经常需要转化模型和关联关系为数组或 JSON。Eloquent 提供了便捷方法以便实现这些转换,以及控制哪些属性被包含到序列化中。
序列化模型&集合
序列化为数组
要转化模型及其加载的关联关系为数组,可以使用 toArray 方法。这个方法是递归的,所以所有属性及其关联对象属性(包括关联的关联)都会被转化为数组:
$user = App\User::with('roles')->first();
return $user->toArray();
还可以转化整个模型集合为数组:
$users = App\User::all();
return $users->toArray();
序列化为 JSON
要转化模型为 JSON,可以使用 toJson 方法,和 toArray 一样,toJson 方法也是递归的,所有属性及其关联属性都会被转化为 JSON:
$user = App\User::find(1);
return $user->toJson();
你还可以转化模型或集合为字符串,这将会自动调用 toJson 方法:
$user = App\User::find(1);
return (string) $user;
由于模型和集合在转化为字符串的时候会被转化为 JSON,你可以从应用的路由或控制器中直接返回 Eloquent 对象:
Route::get('users',function(){
return App\User::all();
});
追加值到 JSON
有时候,需要添加数据库中没有的字段到数组中,要实现这个功能,首先要为这个值定义一个访问器:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 为用户获取管理员标识
*
* @return bool
*/
public function getIsAdminAttribute()
{
return $this->attributes['admin'] == 'yes';
}
}
定义好访问器后,添加字段名到该模型的 appends 属性。需要注意的是,尽快访问器使用“camel case”形式定义,属性名通常以“snake case”的方式被引用:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 追加到模型数组表单的访问器
*
* @var array
*/
protected $appends = ['is_admin'];
}
字段被添加到 appends 列表之后,将会被包含到模型数组和 JSON 中,appends 数组中的属性还会遵循模型中配置的 visible 和 hidden 设置。
Artisan Console —— 控制台命令
简介
Artisan 是 Laravel 自带的命令行接口名称,它为我们在开发过程中提供了很多有用的命令。想要查看所有可用的Artisan命令,可使用list命令:
php artisan list
每个命令都可以用help指令显示命令描述及命令参数和选项。想要查看帮助界面,只需要在命令前加上help就可以了:
php artisan help migrate
编写命令
除了Artisan提供的系统命令之外,还可以构建自己的命令。你可以将自定义命令存放在app/Console/Commands目录;当然,你可以自己选择存放位置,只要该命令可以被Composer自动加载即可。
生成命令
要创建一个新命令,你可以使用Artisan命令make:console,该命令会在 app/Console/Commands 目录下创建一个新的命令类。如果该目录不存在,不用担心,因为它将会在你首次运行Artisan命令 make:command 时被创建。生成的命令将会包含默认的属性设置以及所有命令都共有的方法:
php artisan make:command SendEmails
命令结构
命令生成以后,需要填写该类的signature和description属性,这两个属性在调用list显示命令的时候会被用到。
handle方法在命令执行时被调用,你可以将所有命令逻辑都放在这个方法里面。
注:为了更好地实现代码复用,最佳实践是保持控制台命令的轻量并让它们延迟到应用服务中完成任务。在下面的例子中,注意我们注入了一个服务类来完成发送邮件这样的“繁重”任务。
下面让我们来看一个例子,注意我们可以在命令类的构造函数中注入任何依赖,Laravel 服务提供者将会在构造函数中自动注入所有依赖类型提示:
<?php
namespace App\Console\Commands;
use App\User;
use App\DripEmailer;
use Illuminate\Console\Command;
class SendEmails extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'email:send {user}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send drip e-mails to a user';
/**
* The drip e-mail service.
*
* @var DripEmailer
*/
protected $drip;
/**
* Create a new command instance.
*
* @param DripEmailer $drip
* @return void
*/
public function __construct(DripEmailer $drip)
{
parent::__construct();
$this->drip = $drip;
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->drip->send(User::find($this->argument('user')));
}
}
测试--应用测试
测试JSON API
Laravel 还提供多个帮助函数用于测试 JSON API 及其响应。例如, json、get、post、put、patch 和 delete方法用于通过多种 HTTP 请求方式发出请求。你还可以轻松传递数据和请求头到这些方法。作为开始,我们编写测试来生成 POST 请求到 /user 并断言返回的数据是否是我们所期望的:
<?php
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->json('POST', '/user', ['name' => 'Sally'])
->seeJson(['created' => true,]);
}
}
seeJson 方法将给定数组转化为 JSON,然后验证应用返回的整个 JSON 响应中的 JSON 片段。因此,如果在 JSON 响应中有其他属性,只要给定片段存在的话测试依然会通过。
验证JSON值匹配
如果你想要验证给定数组和应用返回的JSON能够精确匹配,使用 seeJsonEquals 方法:
<?php
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->json('POST', '/user', ['name' => 'Sally'])
->seeJsonEquals(['created' => true,]);
}
}
验证数据结构匹配
还可以验证JSON响应是否与指定数据结构匹配,我们使用 seeJsonStructure 方法来实现这一功能:
<?php
class ExampleTest extends TestCase{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->get('/user/1')
->seeJsonStructure([
'name',
'pet' => [
'name', 'age'
]
]);
}
}
上面的例子演示了期望获取一个包含 name 和嵌套 pet 对象(该对象包含 name 和 age 属性)的 JSON 数据。如果 JSON 响应中包含其它额外键 seeJsonStructure 也不会失败,例如,如果 pet 对象包含 weight 属性测试仍将通过。
你可以使用*来断言返回JSON结构包含一个列表,该列表中的每个数据项都包含至少如下示例中列出的属性:
<?php
class ExampleTest extends TestCase{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
// Assert that each user in the list has at least an id, name and email attribute.
$this->get('/users')
->seeJsonStructure([
'*' => [
'id', 'name', 'email'
]
]);
}
}
你还可以使用嵌套的*,在这种场景中,我们可以断言JSON响应中的每个用户都包含一个给定属性集合,而且每个用户的每个 pet 都包含给定属性集合:
$this->get('/users')
->seeJsonStructure([
'*' => [
'id', 'name', 'email', `pets` => [
'*' => [
'name', 'age'
]
]
]
]);
Session认证
Laravel提供了多个辅助函数用于在测试期间处理Session,首先,可以使用withSession方法设置 session 值到给定数组。这在测试请求前获取 session 数据时很有用:
<?php
class ExampleTest extends TestCase{
public function testApplication()
{
$this->withSession(['foo' => 'bar'])
->visit('/');
}
}
当然,session的最常见的作用就是维护认证用户的状态。辅助函数actingAs 为认证给定用户是当前用户提供了简单的实现方法,例如,我们使用模型工厂生成和认证用户:
<?php
class ExampleTest extends TestCase{
public function testApplication()
{
$user = factory(App\User::class)->create();
$this->actingAs($user)
->withSession(['foo' => 'bar'])
->visit('/')
->see('Hello, '.$user->name);
}
}
还可以通过传递 guard 名称作为 actingAs 函数的第二个参数的方式来指定使用哪个 guard 来认证给定用户:
$this->actingAs($user, 'backend')
数据库
运行 Select 查询
$users = DB::select('select * from users where active = ?', [1]);
使用命名绑定
除了使用?占位符来代表参数绑定外,还可以使用命名绑定来执行查询:
$results = DB::select('select * from users where id = :id', ['id' => 1]);
运行插入语句
使用DB门面的insert方法执行插入语句。和select一样,改方法将原生SQL语句作为第一个参数,将绑定作为第二个参数:
DB::insert('insert into users (id, name) values (?, ?)', [1, 'Dayle']);
运行更新语句
update方法用于更新数据库中已存在的记录,该方法返回受更新语句影响的行数:
$affected = DB::update('update users set votes = 100 where name = ?', ['John']);
运行删除语句
delete方法用于删除数据库中已存在的记录,和update一样,该语句返回被删除的行数:
$deleted = DB::delete('delete from users');
从一张表中取出所有行
$users = DB::table('users')->get();
从一张表中获取一行/一列
$user = DB::table('users')->where('name', 'John')->first();
如果你不需要完整的一行,可以使用value方法从结果中获取单个值,该方法会直接返回指定列的值:
$email = DB::table('users')->where('name', 'John')->value('email');
聚合函数查询:count,max,min,max,avg,sum
$users = DB::table('users')->count();
$price = DB::table('orders')->max('price');
$price = DB::table('orders')
->where('finalized', 1)
->avg('price');
指定查询子句(只查询部分字段)
$users = DB::table('users')->select('name', 'email as user_email')->get();
distinct方法允许你强制查询返回不重复的结果集:
$users = DB::table('users')->distinct()->get();
如果你已经有了一个查询构建器实例并且希望添加一个查询列到已存在的select子句,可以使用addSelect方法:
$query = DB::table('users')->select('name');
$users = $query->addSelect('age')->get();
原生表达式
$users = DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.*', 'contacts.phone', 'orders.price')
->get();
左连接
$users = DB::table('users')
->leftJoin('posts', 'users.id', '=', 'posts.user_id')
->get();
交叉连接:使用crossJoin方法,传递你想要交叉连接的表名到该方法即可。交叉连接在第一张表和被连接表之间生成一个笛卡尔积:
$users = DB::table('sizes')
->crossJoin('colours')
->get();
高级连接语句:传递一个闭包到join方法作为该方法的第二个参数,该闭包将会返回允许你指定join子句约束的JoinClause对象:
DB::table('users')
->join('contacts', function ($join) {
$join->on('users.id', '=', 'contacts.user_id')->orOn(...);
})
->get();
在连接中使用“where”风格的子句,可以在查询中使用where和orWhere方法。这些方法将会将列和值进行比较而不是列和列进行比较:
DB::table('users')
->join('contacts', function ($join) {
$join->on('users.id', '=', 'contacts.user_id')
->where('contacts.user_id', '>', 5);
})
->get();
联合查询(union):比如,你可以先创建一个查询,然后使用union方法将其和第二个查询进行联合:
$first = DB::table('users')
->whereNull('first_name');
$users = DB::table('users')
->whereNull('last_name')
->union($first)
->get();
where语句
例如:验证“votes”列的值是否等于100的查询
$users = DB::table('users')->where('votes', '=', 100)->get();
例如:比较列值和给定数值是否相等
$users = DB::table('users')->where('votes', 100)->get();
还可以传递条件数组到where函数:
$users = DB::table('users')->where([
['status', '=', '1'],
['subscribed', '<>', '1'],
])->get();
whereBetween方法验证列值是否在给定值之间:
$users = DB::table('users')->whereBetween('votes', [1, 100])->get();
whereNotBetween方法验证列值不在给定值之间:
$users = DB::table('users')->whereNotBetween('votes', [1, 100])->get();
whereIn方法验证给定列的值是否在给定数组中:
$users = DB::table('users')
->whereIn('id', [1, 2, 3])
->get();
whereNotIn方法验证给定列的值不在给定数组中:
$users = DB::table('users')
->whereNotIn('id', [1, 2, 3])
->get();
whereNull方法验证给定列的值为NULL:
$users = DB::table('users')
->whereNull('updated_at')
->get();
whereNotNull方法验证给定列的值不是NULL:
$users = DB::table('users')
->whereNotNull('updated_at')
->get();
whereDate方法用于比较字段值和日期:
$users = DB::table('users')
->whereDate('created_at', '2016-10-10')
->get();
whereMonth方法用于比较字段值和一年中的指定月份:
$users = DB::table('users')
->whereMonth('created_at', '10')
->get();
whereDay方法用于比较字段值和一月中的制定天:
$users = DB::table('users')->whereDay('created_at', '10')->get();
whereYear方法用于比较字段值和指定年:
$users = DB::table('users')->whereYear('created_at', '2016')->get();
whereColumn方法用于验证两个字段是否相等:
$users = DB::table('users')->whereColumn('first_name', 'last_name')->get();
排序/分组/限定
orderBy方法允许你通过给定字段对结果集进行排序,orderBy的第一个参数应该是你希望排序的字段,第二个参数控制着排序的方向——asc或desc:
$users = DB::table('users')->orderBy('name', 'desc')->get();
inRandomOrder方法可用于对查询结果集进行随机排序,比如,你可以用该方法获取一个随机用户:
$randomUser = DB::table('users')->inRandomOrder()->first();
想要限定查询返回的结果集的数目,或者在查询中跳过给定数目的结果,可以使用skip和take方法:
$users = DB::table('users')->skip(10)->take(5)->get();
还可以使用limit和offset方法:
$users = DB::table('users')
->offset(10)
->limit(5)
->get();
分页
在查询上调用paginate方法。在本例中,传递给paginate的唯一参数就是你每页想要显示的数目,这里我们指定每页显示15个:
$users = DB::table('users')->paginate(15);
简单分页
在分页视图中简单的显示“下一页”和“上一页”链接,可以使用simplePaginate方法来执行一个更加高效的查询。在渲染包含大数据集的视图且不需要显示每个页码时这一功能非常有用:
$users = DB::table('users')->simplePaginate(15);
显示分页结果
使用Blade显示这些结果并渲染页面链接:
<div class="container">
@foreach ($users as $user)
{{ $user->name }}
@endforeach
</div>
{{ $users->links() }}
links方法将会将结果集中的其它页面链接渲染出来。每个链接已经包含了?page查询字符串变量。
添加参数到分页链接
例如,要添加&sort=votes到每个分页链接,应该像如下方式调用appends:
{{ $users->appends(['sort' => 'votes'])->links() }}
编写数据库迁移文件(建数据表)
生成迁移文件:使用 Artisan 命令make:migration来创建一个新的迁移:
php artisan make:migration create_demo_table
生成的新迁移位于database/migrations目录下,每个迁移文件名都包含时间戳从而允许 Laravel 判断其顺序。
在生成的迁移文件中的up()方法中编写要创建的表结构字段
Schema::create('demo', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->comment('昵称');
$table->string('mobile',20)->unique();
$table->string('password')->comment('密码');
$table->softDeletes();
$table->timestamps();
});
编写完成之后,执行命令就会创建新的一张表
php artisan migrate
更改字段,先输入命令行生成(后面同样)
php artisan make:migration alter_demo_table_name
同样在迁移文件的up()方法中进行编辑(改变)
Schema::table('users', function ($table) {
$table->string('name', 50)->change();
$table->string('name', 50)->nullable()->change();
});
重命名字段
Schema::table('users', function ($table) {
$table->renameColumn('from', 'to');
});
删除字段
//删除单个字段
Schema::table('users', function ($table) {
$table->dropColumn('votes');
});
//删除多个字段
Schema::table('users', function ($table) {
$table->dropColumn(['votes', 'avatar', 'location']);
});
创建索引
创建索引,可以使用unique方法:
$table->string('email')->unique();
可用索引类型
$table->primary('id');
添加主键索引
$table->primary(['first', 'last']);
添加混合索引
$table->unique('email');
添加唯一索引
$table->unique('state', 'my_index_name');
指定自定义索引名称
$table->index('state');
添加普通索引
删除索引
$table->dropPrimary('users_id_primary');
从 “users”表中删除主键索引
$table->dropUnique('users_email_unique');
从 “users”表中删除唯一索引
$table->dropIndex('geo_state_index');
从 “geo”表中删除普通索引
常用的迁移文件数据类型
$table->char('name', 4);
等同于数据库中的CHAR类型
$table->date('created_at');
等同于数据库中的DATE类型
$table->dateTime('created_at');
等同于数据库中的DATETIME类型
$table->dateTimeTz('created_at');
等同于数据库中的DATETIME类型(带时区)
$table->increments('id');
数据库主键自增ID
$table->integer('votes');
等同于数据库中的 INTEGER 类型
$table->longText('description');
等同于数据库中的 LONGTEXT 类型
$table->nullableTimestamps();
和 timestamps()一样但允许 NULL值
$table->string('email');
等同于数据库中的 VARCHAR 列
$table->text('description');
等同于数据库中的 TEXT 类型
$table->time('sunrise');
等同于数据库中的 TIME类型
$table->timestamp('added_on');
等同于数据库中的 TIMESTAMP 类型
$table->timestamps();
添加 created_at 和 updated_at列
数据库-填充数据
在合作开发过程中,执行数据库迁移,只能迁移表的结构,而不能迁移表的内容,因此,需要填充数据后,合作开发的伙伴才可以执行命令,将我们之前填充的数据填充到自己的本地
要生成一个填充器,可以通过 Artisan 命令make:seeder。所有框架生成的填充器都位于database/seeders目录:
php artisan make:seeder DemoTableSeeder
添加一个数据库插入语句到run方法:
DB::table('users')->insert([
'name' => str_random(10),
'email' => str_random(10).'76786',
'password' => bcrypt('secret'),
]);
调用额外的填充器
在DatabaseSeeder类中,你可以使用call方法执行额外的填充类,使用call方法允许你将数据库填充分解成多个文件,这样单个填充器类就不会变得无比巨大,只需简单将你想要运行的填充器类名传递过去即可:
/**
* 运行数据库填充
*
* @return void
*/
public function run(){
$this->call(UserTableSeeder::class);
$this->call(PostsTableSeeder::class);
$this->call(CommentsTableSeeder::class);
}
填充数据完毕之后,接下来是运行填充器:
php artisan db:seed
或
php artisan db:seed --class=UserTableSeeder
本地拉取公司的码云代码库(合作开发)
首先是
git clone 项目的码云地址
完成后,会看到我们的根目录有多了个我们拉取的项目文件
下一步
cd 这个项目
接下里是安装依赖
输入命令 composer install 有时候会出现安装失败多装几次,实在不行的话 换成中国镜像:
输入 composer config -g repo.packagist composer https://packagist.laravel-china.org
同样也行 安装过程比较久 耐心等待!
如果出现问题 这边有个小技巧 ctrl+C断开 然后输入 composer update 进行更新
接下来 看下项目有没有 两个文件 .env 和 env.example
.env
.env.example
如果存在的话 cp .env.example .env 进行复制example 生成.env文件
如果不存在 则重新下载
接下来查看.env文件 key值 是为空的 我们就要为他生成
输入php artisan key:generate
下一步 打开laragon的数据库
创建数据库
然后在.env文件找到数据库配置的地方
回到终端那边输入 php artisan migrate
可以给表填充数据 php artisan db:seed
接下来就可以在浏览器访问项目了