Laravel5.1-Eloquent ORM:起步
概述
有很多朋友问,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命名空间即可:
-
1 namespace App\Models; 2 use ... 3 class User extends Model implements AuthenticatableContract, CanResetPasswordContract 4 { 5 ... 6 }
不过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 --migrationphp artisan make:model Models\User -m
定义模型的内容
用artisan自动生成model以后,典型的内容应该是这样的:
1 <?php 2 namespace App; 3 use Illuminate\Database\Eloquent\Model; 4 class Flight extends Model 5 { 6 // 7 }
下面我们就来看可以在里面写点啥:
表名
首先第一个肯定是关联表了,否则怎么使用ORM呢?
<?phpnamespace 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这两个字段,并会在生成数据的时候自动填充。
如果你的表中没有这两个字段或者不想自动管理它们,可以像下面这样关掉:
<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Flight extends Model{public $timestamps = false;}
你还可以改时间戳在数据库中的存储格式(默认从年存到秒),还有从数据库中读出来的显示格式:
<?phpnamespace 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里面配置过):
<?phpnamespace 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()的;
<?phpnamespace 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();
似乎是理所当然的事情,不解释。
软删除
软删除其实就是假删除,数据都还在,只不过是看不见了,这是非常常用的功能。
要使用软删除,首先要做一下设置:
<?phpnamespace 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()方法里:
<?phpnamespace 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了。

浙公网安备 33010602011771号