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;
    use ...
    class User extends Model implements AuthenticatableContract, CanResetPasswordContract
    {
    ...
    }

不过User 模型因为与Auth还有点关系,所以在config/auth.php里,你还需要修改一下命名空间:

  1. 'model' =>App\Models\User::class,

用artisan生成Model文件

  1. php artisan make:model User

如果像刚才讲的一样,喜欢吧model放到models文件夹里,就这样写:

  1. php artisan make:model Models\User

同时,比较好的习惯是后面再带一个migration,因为建立好模型后就要去建表了,所以生成Model的正确姿势是:

  1. php artisan make:model Models\User --migration
  2. php artisan make:model Models\User -m

定义模型的内容

用artisan自动生成model以后,典型的内容应该是这样的:

 <?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
//
}

下面我们就来看可以在里面写点啥:

表名

首先第一个肯定是关联表了,否则怎么使用ORM呢?

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. protected $table = 'my_flights';
  7. }

这样就把Flight这个模型和my_flights这张表以及表里的字段联系起来了,表的字段会自动成为Flight这个模型对象的属性。

主关键字(Primary Keys)

Laravel默认自增id字段为主Key,当然你也可以指定其他的字段:

  1. $primaryKey ='user_name';

时间戳(Timestamps)

一般情况下,Laravel默认你的表中自带created_atupdated_at这两个字段,并会在生成数据的时候自动填充。

如果你的表中没有这两个字段或者不想自动管理它们,可以像下面这样关掉:

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. public $timestamps = false;
  7. }

你还可以改时间戳在数据库中的存储格式(默认从年存到秒),还有从数据库中读出来的显示格式:

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. protected $dateFormat = 'U';
  7. }

文档这里又是个啃爹货,你以为每个人都懂'U'是什么意思吗?写成'Y-m-d'这种大家都能理解的不好吗?关于'U'是什么意思,以及一共有多少种时间格式,请参阅:

http://php.net/manual/en/datetime.createfromformat.php

数据库连接

一般不写的话是连接默认数据库,但是你也可以把某个模型关联到其他数据库(当然,这个数据库你必须事先在config里面配置过):

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. protected $connection = 'connection-name';
  7. }

获取模型集合

所谓获取模型集合就是获取多条数据库记录对应的模型。获取的方法和数据库查询器(DQB)的方法完全一样的,只不过指定的类名不是DB,而是相应的模型:

  1. namespace App\Http\Controllers;
  2. use App\Flight;
  3. use App\Http\Controllers\Controller;
  4. class FlightController extends Controller
  5. {
  6. public function index()
  7. {
  8. $flights = Flight::all();
  9. return view('flight.index', ['flights' => $flights]);
  10. }
  11. }

这个All()在这里就是把全部记录取出来,也就是自动把对应的模型对象集合全部取出来了。

读取模型属性

字段和字段值就是模型对象的属性和属性值:

  1. foreach ($flights as $flight) {
  2. echo $flight->name;
  3. }

进一步筛选数据

  1. $flights = App\Flight::where('active', 1)
  2. ->orderBy('name', 'desc')
  3. ->take(10)
  4. ->get();

看过DQB后,这些都是最基本的内容了。

Collections

像all()和get()这种会返回多条数据的方法,在Eloquent里面会返回一个collection对象集合(对象装在对象里),而不是像DQB的数组结果集合(对象装在数组里)。Collection其实前面已经讲过了,它是在数据查询出来后,提供了一系列处理数据的方法,非常强大实用。

当然,collection结果集也是可以直接遍历的:

  1. foreach ($flights as $flight) {
  2. echo $flight->name;
  3. }

切片化处理结果(chunk)

处理海量数据的时候,你可以把数据切片化处理,这样节省内存:

  1. Flight::chunk(200, function ($flights) {
  2. foreach ($flights as $flight) {
  3. //
  4. }
  5. });

方法和DQB一样,这里就不多说了。

获取单个模型对象(单条数据)

取单个对象,用find()first()方法:

  1. // 这个是通过ID查找
  2. $flight = App\Flight::find(1);
  3. // 这个是在结果集中取第一个记录
  4. $flight = App\Flight::where('active', 1)->first();

find()方法还可以用于查询多条记录,用数组就行:

  1. $flights = App\Flight::find([1, 2, 3]);

找不到记录怎么办

很多情况下,你希望找不到记录的时候自动报错,这时候就使用findOrFail()firstOrFail()方法,这两个方法首先会去找第一条记录,找不到就会抛出一个Illuminate\Database\Eloquent\ModelNotFoundException类指定的错误,如果这个错误没有被指定抛出,就会自动抛出一个http 404错误:

  1. $model = App\Flight::findOrFail(1);
  2. $model = App\Flight::where('legs', '>', 100)->firstOrFail();

结果计算(Aggregates)

和DQB一样,用于结果计算的count,sum,max这些方法都可以用,不过这些方法返回是数字而不是对象哦:

  1. $count = App\Flight::where('active', 1)->count();
  2. $max = App\Flight::where('active', 1)->max('price');

模型的增删改

  1. namespace App\Http\Controllers;
  2. use App\Flight;
  3. use Illuminate\Http\Request;
  4. use App\Http\Controllers\Controller;
  5. class FlightController extends Controller
  6. {
  7. public function store(Request $request)
  8. {
  9. $flight = new Flight;
  10. $flight->name = $request->name;
  11. $flight->save();
  12. }
  13. }

这个就是最简单的通过模型新增数据库记录的方法了,在save()之前,都是在操作模型数据对象,save()后,模型的属性就被写到数据库的字段里了,注意id,created_at,updated_at字段是默认生成的,不用手动指定;

除了用save()方法,还有一个方法create(),这个方法可以批量更新属性,并直接写入数据库:

  1. public function store(Request $request)
  2. {
  3. $flight = Flight::create([
  4. 'name'=>'MH370',
  5. 'passengers'=>'227',
  6. 'from'=>'KL',
  7. 'to'=>'BJ',
  8. 'status'=>'missing'
  9. ]);
  10. }

如果你的$request对象刚好就是下面那个数组(当然实际不是的,还需要处理一下,具体看请求那一章节),你可以这样写:

  1. public function store(Request $request)
  2. {
  3. $flight = Flight::create($request);
  4. }

是不是变得非常简单了?

但是,有一个问题:

批量更新注入(Mass Assignment)

这个$request是表单或者url参数提交过来的,所以用户可以*设置的.万一你的模型中有这样一个属性'is_admin', 黑客直接把这个参数改为1,然后通过create方法写入数据库,那你不就傻X了么。

所以,为了预防这个问题的发生,在写入数据库之前,还需要校验一下哪些字段是可以通过create()方法批量改的(也就是用户有权利*改的),哪些是不能外部参数直接改的,必须手动指定属性,然后save()的;

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. class Flight extends Model
  5. {
  6. protected $fillable = ['name','passengers'];
  7. }

$fillable顾名思义就是可以填的字段,是个白名单;一般不能填写的字段占少数,为了填写方便,还有个黑名单$guarded:

  1. namespace App;
  2. use Illuminate\Database\Eloquent\Model;
  3. class Flight extends Model
  4. {
  5. protected $guarded = ['status'];
  6. }

也就是说,除了status不能批量更新,其他字段都能批量更新;

注意了:如果你使用了create()方法,但又没有添加上面说的写保护属性,那么会报Mass Assignment的错误,这是无数人遇到过的问题。

  1. $flight = App\Flight::find(1);
  2. $flight->name = 'New Flight Name';
  3. $flight->save();

原理都是很简单的,改属性,然后存数据库。

批量更新

还有一个update()方法,可以对多条数据同时更新:

  1. App\Flight::where('active', 1)
  2. ->where('destination', 'Shanghai')
  3. ->update(['delayed' => 1]);

上面的意思是:所有在飞上海的航班,延误状态改为1;

这个update()和 create()一样,也是对mass assignment写保护是有要求的;

其他生成记录的方法

还有一些常用的逻辑,laravel也封装好了:

  1. // 先找第一条记录,如果没有就新建一条记录(写数据库)
  2. $flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
  3. // 先找第一条记录,如果没有就新建一个对象(不写数据库)
  4. $flight = App\Flight::firstOrNew(['name' => 'Flight 10']);

如果你一直在看这个文档攻略,现在你应该熟悉laravel的语义,一看到create这种词,就知道是拿数据库开刀的。

  1. $flight = App\Flight::find(1);
  2. $flight->delete();

有save()就有delete(),道理很简单,delete()也是直接操作数据库。

通过ID删除记录

  1. App\Flight::destroy(1);
  2. App\Flight::destroy([1, 2, 3]);
  3. App\Flight::destroy(1, 2, 3);

上面的步骤是先找后删除,有个简写的方法,destroy(),你把一个id或多个id输入进去,就可以在数据库中直接删除数据了;

按条件删除

  1. $deletedRows = App\Flight::where('active', 0)->delete();

似乎是理所当然的事情,不解释。

软删除

软删除其实就是假删除,数据都还在,只不过是看不见了,这是非常常用的功能。

要使用软删除,首先要做一下设置:

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. use Illuminate\Database\Eloquent\SoftDeletes;
  5. class Flight extends Model
  6. {
  7. use SoftDeletes;
  8. protected $dates = ['deleted_at'];
  9. }

在模型中引入一个Illuminate\Database\Eloquent\SoftDeletes PHP Trait,然后添加一个属性'deleted_at'

当然,只是模型有'deleted_at'还不够,数据库也必有对应的字段,migration提供了一个方便的写法:

  1. Schema::table('flights', function ($table) {
  2. $table->softDeletes();
  3. });

现在就可以使用软删除了,使用delete()方法时,不是真删,而是假删。

如果你要判断某个模型是否处被软删除了,可以:

  1. if ($flight->trashed()) {
  2. //
  3. }

显而易见,就是trashed()这个方法,也就是暂时丢进回收站,还可以回收的。

查询被软删除(回收站里)的记录

查全部数据

默认情况下,被trashed的记录都不会被查询出来,如果要查全部数据,加个方法withTrashed()就行了:

  1. $flights = App\Flight::withTrashed()
  2. ->where('account_id', 1)
  3. ->get();

只查回收站里的数据

  1. $flights = App\Flight::onlyTrashed()
  2. ->where('airline_id', 1)
  3. ->get();

显而易见,onlyTrashed()。

还原回收站数据

  1. $flight->restore();

这个还原整个模型被软删除的数据;

  1. App\Flight::withTrashed()
  2. ->where('airline_id', 1)
  3. ->restore();

这个是还原指定条件的数据;

强制永久性删除

设置了软删除后,如果要强制删除,可以:

  1. $flight->forceDelete();

Query Scopes

这个scope是范围的意思,Query Scopes讲的就是查询范围,是用来限制模型的读取范围的。
软删除就是个很好的Query Scopes的例子,它默认只查出那些没被软删除的记录;

我们可以自己来定义scope:

  1. namespace App;
  2. use Illuminate\Database\Eloquent\Model;
  3. class User extends Model
  4. {
  5. public function scopePopular($query)
  6. {
  7. return $query->where('votes', '>', 100);
  8. }
  9. public function scopeActive($query)
  10. {
  11. return $query->where('active', 1);
  12. }
  13. }

scopePopular是个魔术方法,这样就定义了一个名为Popular的Scope;

使用scope

  1. $users = App\User::popular()->active()->orderBy('created_at')->get();

只要在模型后跟上那么scope的名字即可;

动态Scope

有时候你需要把数据筛选条件变成动态的,很简单,加个参数就好:

  1. namespace App;
  2. use Illuminate\Database\Eloquent\Model;
  3. class User extends Model
  4. {
  5. public function scopeOfType($query, $type)
  6. {
  7. return $query->where('type', $type);
  8. }
  9. }

用的时候这样用:

  1. $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()方法里:

  1. <?php
  2. namespace App\Providers;
  3. use App\User;
  4. use Illuminate\Support\ServiceProvider;
  5. class AppServiceProvider extends ServiceProvider
  6. {
  7. public function boot()
  8. {
  9. User::creating(function ($user) {
  10. if ( ! $user->isValid()) {
  11. return false;
  12. }
  13. });
  14. }
  15. public function register()
  16. {
  17. //
  18. }
  19. }

一旦有模型新建记录的事件发生,就会先运行闭包里的代码。

creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored.至于这些事件的具体定义,就不多说了,看字面意思就ok了。

上一篇:BestCoder Round #74


下一篇:使用Java判断字符串中的中文字符数量