概述
有很多朋友问,MCV的M是在哪里介绍的,这里就是介绍M的地方了。
Laravel有一个强大的数据库ORM Eloquent,它的原理是每张数据表对应一个Model,对Model的操作就对应数据库的操作,你只用管对model的操作,而数据库的操作是自动的(意味着你不用写SQL语句)。
Eloquent采用了Active Record的模式,表映射到类,记录映射到对象。它的特点是简单直观,但解耦方面稍弱。还有一种叫做Data Mapping(以Doctrine为代表),它对象操作和数据操作是完全分离的,有兴趣可以google一下。
使用Eloquent之前,先配置一下数据库连接。
定义数据模型(Models)
新装好的Laravel App目录下,你会发现已经有一个User.php的模型文件了,在这里会出现一个无数人问的问题:为什么把模型文件放在这里?没有一个Models的目录吗?
我想Taylor可能是觉得组织Model的方式有很多种,把选择权交给大家吧;
按照我自己的习惯,我是会把Model放进app目录下新建的Models文件夹里,怎么弄呢?
你只需要改一下User Model命名空间即可:
namespace App\Models;
use ...
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
...
}
不过User 模型因为与Auth还有点关系,所以在config/auth.php
里,你还需要修改一下命名空间:
'model' =>App\Models\User::class,
用artisan生成Model文件
php artisan make:model User
如果像刚才讲的一样,喜欢吧model放到models文件夹里,就这样写:
php artisan make:model Models\User
同时,比较好的习惯是后面再带一个migration,因为建立好模型后就要去建表了,所以生成Model的正确姿势是:
php artisan make:model Models\User --migration
php artisan make:model Models\User -m
定义模型的内容
用artisan自动生成model以后,典型的内容应该是这样的:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
//
}
下面我们就来看可以在里面写点啥:
表名
首先第一个肯定是关联表了,否则怎么使用ORM呢?
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
protected $table = 'my_flights';
}
这样就把Flight这个模型和my_flights这张表以及表里的字段联系起来了,表的字段会自动成为Flight这个模型对象的属性。
主关键字(Primary Keys)
Laravel默认自增id字段为主Key,当然你也可以指定其他的字段:
$primaryKey ='user_name';
时间戳(Timestamps)
一般情况下,Laravel默认你的表中自带created_at
和updated_at
这两个字段,并会在生成数据的时候自动填充。
如果你的表中没有这两个字段或者不想自动管理它们,可以像下面这样关掉:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
public $timestamps = false;
}
你还可以改时间戳在数据库中的存储格式(默认从年存到秒),还有从数据库中读出来的显示格式:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
protected $dateFormat = 'U';
}
文档这里又是个啃爹货,你以为每个人都懂'U'
是什么意思吗?写成'Y-m-d'
这种大家都能理解的不好吗?关于'U'
是什么意思,以及一共有多少种时间格式,请参阅:
http://php.net/manual/en/datetime.createfromformat.php
数据库连接
一般不写的话是连接默认数据库,但是你也可以把某个模型关联到其他数据库(当然,这个数据库你必须事先在config里面配置过):
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
protected $connection = 'connection-name';
}
获取模型集合
所谓获取模型集合就是获取多条数据库记录对应的模型。获取的方法和数据库查询器(DQB)的方法完全一样的,只不过指定的类名不是DB,而是相应的模型:
namespace App\Http\Controllers;
use App\Flight;
use App\Http\Controllers\Controller;
class FlightController extends Controller
{
public function index()
{
$flights = Flight::all();
return view('flight.index', ['flights' => $flights]);
}
}
这个All()在这里就是把全部记录取出来,也就是自动把对应的模型对象集合全部取出来了。
读取模型属性
字段和字段值就是模型对象的属性和属性值:
foreach ($flights as $flight) {
echo $flight->name;
}
进一步筛选数据
$flights = App\Flight::where('active', 1)
->orderBy('name', 'desc')
->take(10)
->get();
看过DQB后,这些都是最基本的内容了。
Collections
像all()和get()这种会返回多条数据的方法,在Eloquent里面会返回一个collection对象集合(对象装在对象里),而不是像DQB的数组结果集合(对象装在数组里)。Collection其实前面已经讲过了,它是在数据查询出来后,提供了一系列处理数据的方法,非常强大实用。
当然,collection结果集也是可以直接遍历的:
foreach ($flights as $flight) {
echo $flight->name;
}
切片化处理结果(chunk)
处理海量数据的时候,你可以把数据切片化处理,这样节省内存:
Flight::chunk(200, function ($flights) {
foreach ($flights as $flight) {
//
}
});
方法和DQB一样,这里就不多说了。
获取单个模型对象(单条数据)
取单个对象,用find()
和first()
方法:
// 这个是通过ID查找
$flight = App\Flight::find(1);
// 这个是在结果集中取第一个记录
$flight = App\Flight::where('active', 1)->first();
find()方法还可以用于查询多条记录,用数组就行:
$flights = App\Flight::find([1, 2, 3]);
找不到记录怎么办
很多情况下,你希望找不到记录的时候自动报错,这时候就使用findOrFail()
和firstOrFail()
方法,这两个方法首先会去找第一条记录,找不到就会抛出一个Illuminate\Database\Eloquent\ModelNotFoundException
类指定的错误,如果这个错误没有被指定抛出,就会自动抛出一个http 404错误:
$model = App\Flight::findOrFail(1);
$model = App\Flight::where('legs', '>', 100)->firstOrFail();
结果计算(Aggregates)
和DQB一样,用于结果计算的count,sum,max这些方法都可以用,不过这些方法返回是数字而不是对象哦:
$count = App\Flight::where('active', 1)->count();
$max = App\Flight::where('active', 1)->max('price');
模型的增删改
增
namespace App\Http\Controllers;
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class FlightController extends Controller
{
public function store(Request $request)
{
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
}
这个就是最简单的通过模型新增数据库记录的方法了,在save()
之前,都是在操作模型数据对象,save()
后,模型的属性就被写到数据库的字段里了,注意id,created_at,updated_at
字段是默认生成的,不用手动指定;
除了用save()方法,还有一个方法create(),这个方法可以批量更新属性,并直接写入数据库:
public function store(Request $request)
{
$flight = Flight::create([
'name'=>'MH370',
'passengers'=>'227',
'from'=>'KL',
'to'=>'BJ',
'status'=>'missing'
]);
}
如果你的$request对象刚好就是下面那个数组(当然实际不是的,还需要处理一下,具体看请求那一章节),你可以这样写:
public function store(Request $request)
{
$flight = Flight::create($request);
}
是不是变得非常简单了?
但是,有一个问题:
批量更新注入(Mass Assignment)
这个$request是表单或者url参数提交过来的,所以用户可以*设置的.万一你的模型中有这样一个属性'is_admin'
, 黑客直接把这个参数改为1,然后通过create方法写入数据库,那你不就傻X了么。
所以,为了预防这个问题的发生,在写入数据库之前,还需要校验一下哪些字段是可以通过create()方法批量改的(也就是用户有权利*改的),哪些是不能外部参数直接改的,必须手动指定属性,然后save()的;
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
protected $fillable = ['name','passengers'];
}
$fillable
顾名思义就是可以填的字段,是个白名单;一般不能填写的字段占少数,为了填写方便,还有个黑名单$guarded
:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
protected $guarded = ['status'];
}
也就是说,除了status不能批量更新,其他字段都能批量更新;
注意了:如果你使用了create()
方法,但又没有添加上面说的写保护属性,那么会报Mass Assignment的错误,这是无数人遇到过的问题。
改
$flight = App\Flight::find(1);
$flight->name = 'New Flight Name';
$flight->save();
原理都是很简单的,改属性,然后存数据库。
批量更新
还有一个update()
方法,可以对多条数据同时更新:
App\Flight::where('active', 1)
->where('destination', 'Shanghai')
->update(['delayed' => 1]);
上面的意思是:所有在飞上海的航班,延误状态改为1;
这个update()和 create()一样,也是对mass assignment写保护是有要求的;
其他生成记录的方法
还有一些常用的逻辑,laravel也封装好了:
// 先找第一条记录,如果没有就新建一条记录(写数据库)
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
// 先找第一条记录,如果没有就新建一个对象(不写数据库)
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
如果你一直在看这个文档攻略,现在你应该熟悉laravel的语义,一看到create这种词,就知道是拿数据库开刀的。
删
$flight = App\Flight::find(1);
$flight->delete();
有save()就有delete(),道理很简单,delete()也是直接操作数据库。
通过ID删除记录
App\Flight::destroy(1);
App\Flight::destroy([1, 2, 3]);
App\Flight::destroy(1, 2, 3);
上面的步骤是先找后删除,有个简写的方法,destroy(),你把一个id或多个id输入进去,就可以在数据库中直接删除数据了;
按条件删除
$deletedRows = App\Flight::where('active', 0)->delete();
似乎是理所当然的事情,不解释。
软删除
软删除其实就是假删除,数据都还在,只不过是看不见了,这是非常常用的功能。
要使用软删除,首先要做一下设置:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Flight extends Model
{
use SoftDeletes;
protected $dates = ['deleted_at'];
}
在模型中引入一个Illuminate\Database\Eloquent\SoftDeletes
PHP Trait,然后添加一个属性'deleted_at'
;
当然,只是模型有'deleted_at'
还不够,数据库也必有对应的字段,migration提供了一个方便的写法:
Schema::table('flights', function ($table) {
$table->softDeletes();
});
现在就可以使用软删除了,使用delete()方法时,不是真删,而是假删。
如果你要判断某个模型是否处被软删除了,可以:
if ($flight->trashed()) {
//
}
显而易见,就是trashed()这个方法,也就是暂时丢进回收站,还可以回收的。
查询被软删除(回收站里)的记录
查全部数据
默认情况下,被trashed的记录都不会被查询出来,如果要查全部数据,加个方法withTrashed()
就行了:
$flights = App\Flight::withTrashed()
->where('account_id', 1)
->get();
只查回收站里的数据
$flights = App\Flight::onlyTrashed()
->where('airline_id', 1)
->get();
显而易见,onlyTrashed()。
还原回收站数据
$flight->restore();
这个还原整个模型被软删除的数据;
App\Flight::withTrashed()
->where('airline_id', 1)
->restore();
这个是还原指定条件的数据;
强制永久性删除
设置了软删除后,如果要强制删除,可以:
$flight->forceDelete();
Query Scopes
这个scope是范围的意思,Query Scopes讲的就是查询范围,是用来限制模型的读取范围的。
软删除就是个很好的Query Scopes的例子,它默认只查出那些没被软删除的记录;
我们可以自己来定义scope:
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
public function scopeActive($query)
{
return $query->where('active', 1);
}
}
scopePopular
是个魔术方法,这样就定义了一个名为Popular的Scope;
使用scope
$users = App\User::popular()->active()->orderBy('created_at')->get();
只要在模型后跟上那么scope的名字即可;
动态Scope
有时候你需要把数据筛选条件变成动态的,很简单,加个参数就好:
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function scopeOfType($query, $type)
{
return $query->where('type', $type);
}
}
用的时候这样用:
$users = App\User::ofType('admin')->get();
Laravel 5.2 Query Scope 改进了很多,变得十分强大和方便。
模型事件
模型事件就是模型在进行数据读写时候发生的事件,例如creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored.
这些。你可以在发生这些行为的时候执行而外的代码逻辑。
不像JS的事件是初始化就会自动侦听的,laravel的事件一般需要预置一个侦听器(listener),如果你要全局侦听,最好的地方就是放在Service Provider的boot()方法里:
<?php
namespace App\Providers;
use App\User;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
User::creating(function ($user) {
if ( ! $user->isValid()) {
return false;
}
});
}
public function register()
{
//
}
}
一旦有模型新建记录的事件发生,就会先运行闭包里的代码。
creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored.
至于这些事件的具体定义,就不多说了,看字面意思就ok了。