Laravel—Eloquent ORM

模型

Laravel中每张数据表都对应一个与该表进行交互的模型(Model),通过模型类,可以对数据表进行查询、插入、更新、删除等操作。

定义模型

模型类通常位于app目录下,所有的Eloquent模型都继承自Illuminate\Database\Eloquent\Model
make:model的Artisan命令用于创建模型,加上-m--migration表示生成模型时生成数据库迁移

php artisan make:model 自定义模型名称 (-m/–migration)

还可以自己指定的生成专门放模型的文件件:

php artisan make:model Model/Test

生成的文件:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Test extends Model
{
    //
}

模型限定

表名

默认是小写规则的模型类名复数格式作为与其对应的表名,可以使用模型定义中的$table属性来自定义表名

主键

默认每张表的主键为id,可以使用$primaryKey属性自定义主键名
Eloquent还默认主键字段是自增的整型数据,使用非自增或非数字类型主键,必须在对应模型中设置$incrementing属性为 false,如果主键不是整型,还要设置$keyType属性值为 string

时间戳

模型默认会在表中创建created_atupdated_at两个字段,如果不想要可以设置$timestamps属性为false
自定义用于存储时间戳的字段名称,可以在模型中设置CREATED_ATUPDATED_AT常量

数据库连接

默认情况下Eloquent模型使用配置中的默认数据库连接,可以使用$connection属性设置不同的连接

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Test extends Model
{
    //自定义表名
    protected $table = 'TestTable';

    //自定义主键名
    public $primaryKey = 'TestId';

    //关闭时间戳字段
    public $timestamps = false;

    //自定义时间戳字段名
    const CREATED_AT = 'creation_date';
    const UPDATED_AT = 'last_uupdate';

    //自定义连接的数据库
    protected $connection = 'connection-name';
}

获取模型

使用use App\表名将相应的数据表模型引入,之后就可以进行查询等操作
all方法用于返回模型表的说有结果

<?php

use App\Flight;

$flights = App\Flight::all();

foreach ($flights as $flight) {
    echo $flight->name;
}

也可以在变量中导入类,也可以添加约束条件到查询,使用get方法获取

$flights = App\Flight::where('active','=',1)
                ->orderBy('namej','desc')
                ->take(10)
                ->get();

由于Eloquent模型本质上就是查询构建器,可以在Eloquent查询中使用查询构建器的所有方法

获取集合

对Eloquent中获取多个结果的方法(比如 all 和 get)而言,其返回值是Illuminate\Database\Eloquent\Collection的一个实例,Collection类提供了多个有用的函数来处理 Eloquent 结果集

组块结果集

chunk方法获取一个指定数量的Eloquent模型组块,并将其填充到给定闭包进行处理,传递给该方法的第一个参数是 想要获取的“组块”数目闭包作为第二个参数被传入用于 处理每个从数据库获取的组块数据,该处理大量数据集合时能够有效减少内存消耗

Flight::chunk(200,function($flights){
    foreach ($flights as $flght) {
        //
    }
});

游标
cursor方法使用游标迭代处理数据库记录,一次只执行单个查询,在处理大批量数据时,该方法可大幅减少内存消耗:

foreach (Flight::where('foo','bar')->cursor() as $flght){
    //
}

获取单个模型/聚合结果

findfirst方法获取单个记录,这些方法返回单个模型实例而不是模型集合
find的参数为 主键id

// 通过主键取回一个模型...
$flight = App\Flight::find(1);

//通过传递主键数组来调用 find 方法,这将会返回匹配记录集合
$flights = App\Flight::find([1,2,3]);

// 取回符合查询限制的第一个模型...
$flight = App\Flight::where('active', 1)->first();

Not Found异常

findOrFailfirstOrFail方法会获取查询到的第一个结果。不过,如果没有任何查询结果,Illuminate\Database\Eloquent\ModelNotFoundException异常将会被抛出:

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

模型的插入

向模型中插入数据时,需要先new创建一个模型实例,然后设置模型的各种属性,最后使用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
     * @translator laravelacademy.org
     */
    public function store(Request $request)
    {
        //先实例化一个对象
        $flight = new Flight;

        $flight->name = $request->name;
        //最后调用save方法保存
        $flight->save();

        /*created_at和updated_at方法会在调用save方法时自动设置*/
    }
}

批量赋值

create方法可以创建一个新的模型,该方法返回被插入的模型实例
在此之前,你需要指定模型的fillableguarded属性,因为所有 Eloquent 模型都通过批量赋值(Mass Assignment)进行保护,这两个属性分别用于定义哪些模型字段允许批量赋值以及哪些模型字段是受保护的,不能显式进行批量赋值

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 可以被批量赋值的属性.
     *
     * @var array
     */
    protected $fillable = ['name'];
}

设置完之后就可以使用create方法在数据库中插入新数据了

$flight = App\Flight::create([‘name’=>’New Name’]);

如果已经有了一个模型实例,可以使用fill方法通过数组属性来填充:

$flight->fill([‘name’=>’New Name’]);

黑名单属性

$fillable就像是可以被赋值属性的“白名单”,还可以选择使用$guarded$guarded属性包含不想被赋值的属性数组。所以不被包含在其中的属性都是可以被赋值的,因此,$guarded功能就像“黑名单”。这两个属性能同时使用其中一个而不能一起使用,因为它们是互斥的。如果想让所有属性可批量赋值,可以使用空数组

protected $guarded = [];

其他创建模型的方法

firstOrCreate/firstOrNew

firstOrCreate方法先尝试通过给定列/值对在数据库中查找记录,如果没有找到的话则通过给定属性创建一个新的记录。

firstOrNew先尝试在数据库中查找匹配的记录,如果没有找到,则返回一个新的模型实例。需要注意的是,通过 firstOrNew方法返回的模型实例并没有持久化到数据库中,需要调用 save 方法手动持久化

// 通过属性获取航班, 如果不存在则创建...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);

// 通过name获取航班,如果不存在则通过name和delayed属性创建...
$flight = App\Flight::firstOrCreate(
    ['name' => 'Flight 10'], ['delayed' => 1]
);

// 通过属性获取航班, 如果不存在初始化一个新的实例...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);

// 通过name获取,如果不存在则通过name和delayed属性创建新实例...
$flight = App\Flight::firstOrNew(
    ['name' => 'Flight 10'], ['delayed' => 1]
);
$flight->save();

updateOrCreate

updateOrCreate方法会判度模型是否存在,如果模型已存在则更新,否则创建新模型的场景。和 firstOrCreate 方法一样,updateOrCreate 方法会持久化模型,所以无需调用 save():

// 如果有从奥克兰到圣地亚哥的航班则将价格设置为 $99
// 如果没有匹配的模型则创建之
$flight = App\Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99]
);

第一个数组的内容为判断+插入的内容,第二个数据为插入的内容

模型的更新

更新模型时,只需要先获取所需要的模型,然后重新赋值,再调用save方法即可,updated_at字段会被自动更新

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

批量更新

使用where条件加上update可以实现批量赋值,update方法要求以数组形式传递键值对参数,代表数据中应该被更新的列

App\Flight::where('name','=','xiaoming')
        ->where('age',5)
        ->update(['age'=>6]);

模型的删除

可以通过find查询id或where条件判断后使用delete方法删除,在知道id的情况下也可以直接用destroy删除

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

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

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

软删除

除了真实删除数据库的记录,Eloquent 也可以“软删除”模型。软删除的模型并不是真的从数据库里删除了。其实是,模型上设置了deleted_at属性并把其值写入数据库。如果deleted_at值非空,代表已软删除这个模型。当查询一个使用软删除的模型时,被软删除的模型将会自动从查询结果中排除。要开启模型的软删除功能,可以在模型上使用Illuminate\Database\Eloquent\SoftDeletes trait并把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添加到数据列,使用softDeletes方法:

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

判断给定模型是否被软删除,使用trashed方法:

if ($flight->trashed())

如果想要软删除模型出现在查询结果中,可以使用withTrashed方法:

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

onlyTrashed方法只获取软删除模型:

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

restore方法可以用来快速恢复多个软删除模型

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

$flight->restore();

forceDelete用于从数据库永久删除模型

$flight->forceDelete();

模型关联

定义关联关系

使用时,先在模型文件那里用函数定义关联方式,之后就可以在路由或控制器中使用这些函数进行关联表的操作

一对一

hasOne方法用于定义一对一的关系模型,第一个参数为关联的模型,第二个参数为外键名称(默认为xxx_id),第三个参数为当前所在表的主键名(默认为id)

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 获取与用户关联的电话号码记录。
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

使用时,只需在控制器或路由使用以下方式

$phone = User::find(1)->phone()->where(…)->get();
$phone = User::find(1)->phone;

属于关系

belongsTo方法定义当前所在模型从属于某个模型,第一个参数为模型名,第二个参数为当前模型的外键名(默认为模型名_id),第三个参数为父模型的主键名(默认为id)

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Phone extends Model{
    /**
     * 获取拥有该手机的用户
     */
    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

默认模型

belongsTo模型可以在给定关联关系为null时定义一个默认的返回模型,这种模式称为空对象模式,使用withDefault方法,在其中还可以传递数据或闭包对默认模型进行填充

public function user()
{
    return $this->belongsTo('App\User')->withDefault();

    return $this->belongsTo('App\User')->withDefault([
        'name'=>'Guest Author',
    ]);

    return $this->belongsTo('App\User')->withDefault(function($user)
    {
        $user->name = 'Guest Author';
    });
}

一对多

hasMany方法用于定义一对多的关系,第一个参数为关联的模型,第二个参数为外键名称(默认为xxx_id),第三个参数为当前所在表的主键名(默认为id)

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model{
    /**
     * 获取博客文章的评论
     */
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
}

使用方式:

$comments = App\Post::find(1)->comments;

foreach($comments as $comment){
    //
}


$comments = App\Post::find(1)->comments()->where('title','=','foo')->first();

多对多

定义多对多关系时,需要一张中间表,中间表的字段分别对应关系表中多对多的字段id
belongsToMany方法用于定义多对多关系,第一个参数为当前模型名,第二个参数为中间表名(默认为以字母顺序连接两个关联模型的名字),第三个参数是定义此关联的模型在连接表里的外键名,第四个参数是另一个模型在连接表里的外键名

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model{
    /**
     * 用户角色
     */
    public function roles()
    {
        return $this->belongsToMany('App\Role');
    }
}

使用方式:

$user = App\User::find(1);

foreach($user->roles as $role){
    //
}


$roles = App\User::find(1)->roles()->orderBy('name')->get();

获取中间表字段

可以通过在访问的模型后面使用pivot属性访问中间表的字段

$user = App\User::find(1);

foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}

默认情况下只有模型的主键才能使用pivot属性,其他字段必须在定义关联时指出才能使用:

return $this->belongsToMany(‘App\Role’)->withPivot(‘column1’,’column2’);

如果想让pivot表自动包含created_at,updated_at时间戳,在定义关联时使用withTimastamps方法:

return $this->belongsToMany(‘App\Role’)->withTimestamps();

as方法可以把pivot的属性名自定义

//使用时就可以用xxxx替代pivot获取中间表字段的值
return $this->belongsToMany('App\Podast')
            ->as('xxxx')
            ->withTimestamps();

通过中间表字段过滤关联关系

在定义关联关系时使用wherePivotwherePivotIn方法过滤belongsToMany返回的结果集

return $this->belongsToMany('App\Role')->wherePivot('approved',1);

return $this->belongsToMany('App\Role')->wherePivotIn('priority',[1,2]);

远程一对多

A关联B,B关联C,可以通过A访问到C的内容,使用hasManyThrough方法,第一个参数是最终希望访问的模型的名称,第二个参数是中间模型名称。
当执行这种关联查询时通常 Eloquent 外键规则会被使用,如果你想要自定义该关联关系的外键,可以将它们作为第三个、第四个参数传递给hasManyThrough 方法。第三个参数是中间模型的外键名,第四个参数是最终模型的外键名,第五个参数是本地主键。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Country extends Model{
    /**
     * 获取指定国家的所有文章
     */
    public function posts()
    {
        return $this->hasManyThrough('App\Post', 'App\User');
    }
}


class Country extends Model
{
    public function posts()
    {
        return $this->hasManyThrough(
            'App\Post',
            'App\User',
            'country_id', // users表使用的外键...
            'user_id', // posts表使用的外键...
            'id', // countries表主键...
            'id' // users表主键...
        );
    }
}

关联查询

可以在关联关系上使用任何查询构建器提供的任何的方法

查询存在的关联

hasorHas方法用于判断模型是否存在关联

//获取所有至少有一条评论的文章
$post = App\Post::has('comoment')->get();

//获取至少有三条评论的文章
$posts = App\Post::has('comments','>=',3)->get();

//获取至少有一条评论并获得投票的文章
$posts = Post::has('comments.votes')->get();

whereHasorWhereHas方法用于自定义约束条件

//获取所有至少有一条评论包含foo字样的文章
$posts = Post::whereHas('comments',function($query)
{
    $query->where('content','like','foo%');
})->get();

查看没有关联的结果

doesntHave,orDoesntHave方法可以查询没有关联关系的模型

$posts = App\Post::doesntHave(‘comments’)->get();
whereDoesntHave,orWhereDoesntHave方法用于添加查询条件

$posts = App\Post::whereDoesntHave('comments',function($query)
{
    $query->where('content','like','foo%');
})->get();

统计关联模型

withCount方法在不加载关联关系的情况下统计关联结果数目,该方法会放置一个{relation}_conut字段到结果模型

$posts = App\Post::withCount('comments')->get();

foreach ($posts as $post) {
    显示每一个post的评论数
    echo $post->comments_count;
}

//可以像添加约束条件到查询一样来添加多个关联关系的计数
$posts = Post::withCount(['votes', 'comments' => function ($query) {
    $query->where('content', 'like', 'foo%');
}])->get();

echo $posts[0]->votes_count;
echo $posts[0]->comments_count;

渴求式加载(预加载)

当作为属性访问模型关联时,关联的数据是“懒加载”。意味着关联的数据在第一次访问该属性的时候才会加载。不过,当查询父模型时,Eloquent可以“预加载”关联数据。 预加载避免了 N + 1 次查询的问题

  • N+1次查询:一次查询模型本身+N次查询关联关系

with方法指定哪些关联应该被预加载

$books = App\book::with('author')->get();

foreach ($books as $book) {
    echo $book->author->name;
}

//在一次操作中预加载多个不同的关联
$books = App\Book::with(['author', 'publisher'])->get();

//预加载所有书籍的作者和这些作者的个人联系信息
$books = App\Book::with('author.contacts')->get();

//在关联中指定想要查询的列:
//这个方法中id字段必须列出
$books = App\Book::with('author:id,name')->get();

这个操作,只执行了两次查询:

select * from books
select * from authors where id in (1, 2, 3, 4, 5, …)

约束预加载

可以指定条件进行预加载,Eloquent只会加载指定条件的模型

$users = App\User::with(['posts' => function ($query) {
    $query->where('title', 'like', '%first%');
}])->get();

延迟预加载

load方法用于在检索出来的模型上进行预加载处理

$books = App\Book::all();

if ($someCondition) {
    $books->load('author','publisher');

    $books->load(['author'=>function($query){
        $query->orderBy('published_date','asc');
    }]);
}

loadMissing方法可以仅在未加载关联时进行加载:

puboic function format(Book $book)
{
    $book->loadMissing('author');

    return [
        'name' => $book->name,
        'author' => $book->author->name
    ];
}

插入、更新关联模型

save方法

先new一个新的要插入的模型对象,写入相应的内容,然后指定相应的被插入模型,调用其关联模型后使用save方法,新对象作为参数

$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$post->comment()->save($comment);

保存多个关联模型,可以使用saveMany方法:

$post = App\Post::find(1);

$post->comments()->saveMany([
    new App\Comment(['message'=>'A new comment.']),
    new App\Comment(['massage'=>'Another comment.']),
]);

create方法

savecreate的不同之处在于save接收整个 Eloquent 模型实例而create收原生 PHP 数组(需要先批量赋值设定):

$post = App\Post::find(1);

$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);

还可以使用 createMany 方法来创建多个关联模型:

$post = App\Post::find(1);

$post->comments()->createMany([
    [
        'message' => 'A new comment.',
    ],
    [
        'message' => 'Another new comment.',
    ],
]);

多对多关联

即在中间表中插入一条数据

//roles为中间表
$user = App\User::find(1);
$user->roles()->attach($roleId);

//在将关系附加到模型时,还可以传递一组要插入到中间表中的附加数据:
$user->roles()->attach($roleId, ['expires' => $expires]);

detach用于移除多对多关联记录

$user->roles()->datach($roleId);

//移除所有
$user->roles()->detach();

attachdetach也允许传递一个 ID 数组:

$user = App\User::find(1);

$user->roles()->detach([1, 2, 3]);

$user->roles()->attach([
    1 => ['expires' => $expires],
    2 => ['expires' => $expires]
]);

同步关联

一次性在中间表中插入对一个表中字段关联多条另一个多对多关系表的字段
sync用于创建多对多关联,接受一个 ID 数组以替换中间表的记录。中间表记录中,所有未在 ID 数组中的记录都将会被移除。只有给出的数组的id会被保留在中间表中

$user->roles()->sync([1,2,3]);

//也可以通过 ID 传递额外的附加数据到中间表
$user->roles()->sync([1=>['expires'=>true],2,3]);

如果不想移除现有的 ID,可以使用syncWithoutDetaching方法:

$user->roles()->syncWithoutDetaching([1, 2, 3]);