Laravel5.7 Eloquent ORM快速入门详解
简介
Laravel内置的EloquentORM提供了一个美观、简单的与数据库打交道的ActiveRecord实现,每张数据表都对应一个与该表进行交互的模型(Model),通过模型类,你可以对数据表进行查询、插入、更新、删除等操作。
在开始之前,确保在config/database.php文件中配置好了数据库连接。更多关于数据库配置的信息,请查看文档。
定义模型
我们从创建一个Eloquent模型开始,模型类通常位于app目录下,你也可以将其放在其他可以被composer.json文件自动加载到的地方。所有Eloquent模型都继承自Illuminate\Database\Eloquent\Model类。
创建模型实例最简单的办法就是使用Artisan命令make:model:
phpartisanmake:modelFlight
如果你想要在生成模型时生成数据库迁移,可以使用--migration或-m选项:
phpartisanmake:modelFlight--migration phpartisanmake:modelFlight-m
Eloquent模型约定
现在,让我们来看一个Flight模型的例子,我们将用该类获取和存取数据表flights中的信息:
表名
注意我们并没有告诉Eloquent我们的Flight模型使用哪张表,默认规则是小写的模型类名复数格式作为与其对应的表名(除非在模型类中明确指定了其它名称)。所以,在本例中,Eloquent认为Flight模型存储记录在flights表中。你也可以在模型中定义table属性来指定自定义的表名:
主键
Eloquent默认每张表的主键名为id,你可以在模型类中定义一个$primaryKey属性来覆盖该约定。
此外,Eloquent默认主键字段是自增的整型数据,这意味着主键将会被自动转化为int类型,如果你想要使用非自增或非数字类型主键,必须在对应模型中设置$incrementing属性为false,如果主键不是整型,还要设置$keyType属性值为string。
时间戳
默认情况下,Eloquent期望created_at和updated_at已经存在于数据表中,如果你不想要这些Laravel自动管理的数据列,在模型类中设置$timestamps属性为false:
如果你需要自定义时间戳格式,设置模型中的$dateFormat属性。该属性决定日期被如何存储到数据库中,以及模型被序列化为数组或JSON时日期的格式:
如果你需要自定义用于存储时间戳的字段名称,可以在模型中设置CREATED_AT和UPDATED_AT常量:
数据库连接
默认情况下,所有的Eloquent模型使用应用配置中的默认数据库连接,如果你想要为模型指定不同的连接,可以通过$connection属性来设置:
获取模型
创建完模型及其关联的数据表后,就可以从数据库中获取数据了。将Eloquent模型看作功能强大的查询构建器,你可以使用它来流畅的查询与其关联的数据表。例如:
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($flightsas$flight){ echo$flight->name; }组块结果集
如果你需要处理数据量很大的Eloquent结果集,可以使用chunk方法。chunk方法会获取一个指定数量的Eloquent模型“组块”,并将其填充到给定闭包进行处理。使用chunk方法在处理大量数据集合时能够有效减少内存消耗:
Flight::chunk(200,function($flights){ foreach($flightsas$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]);NotFound异常
有时候你可能想要在模型找不到的时候抛出异常,这在路由或控制器中非常有用,findOrFail和firstOrFail方法会获取查询到的第一个结果。不过,如果没有任何查询结果,Illuminate\Database\Eloquent\ModelNotFoundException异常将会被抛出:
$model=App\Flight::findOrFail(1); $model=App\Flight::where('legs','>',100)->firstOrFail();如果异常没有被捕获,那么HTTP404响应将会被发送给用户,所以在使用这些方法的时候没有必要对返回404响应编写额外的检查:
Route::get('/api/flights/{id}',function($id){ returnApp\Flight::findOrFail($id); });获取聚合结果
当然,你还可以使用查询构建器提供的聚合方法,例如count、sum、max,以及其它查询构建器提供的聚合函数。这些方法返回计算后的结果而不是整个模型实例:
$count=App\Flight::where('active',1)->count(); $max=App\Flight::where('active',1)->max('price');插入/更新模型
插入
想要在数据库中插入新的记录,只需创建一个新的模型实例,设置模型的属性,然后调用save方法:
name=$request->name; $flight->save(); } }在这个例子中,我们只是简单分配HTTP请求中的name参数值给App\Flight模型实例的name属性,当我们调用save方法时,一条记录将会被插入数据库。created_at和updated_at时间戳在save方法被调用时会自动被设置,所以没必要手动设置它们。
更新
save方法还可以用于更新数据库中已存在的模型。要更新一个模型,应该先获取它,设置你想要更新的属性,然后调用save方法。同样,updated_at时间戳会被自动更新,所以没必要手动设置其值:
$flight=App\Flight::find(1); $flight->name='NewFlightName'; $flight->save();批量更新
更新操作还可以同时修改给定查询提供的多个模型实例,在本例中,所有有效且destination=SanDiego的航班都被标记为延迟:
App\Flight::where('active',1) ->where('destination','SanDiego') ->update(['delayed'=>1]);update方法要求以数组形式传递键值对参数,代表着数据表中应该被更新的列。
注:通过Eloquent进行批量更新时,saved和updated模型事件将不会在更新模型时触发。这是因为在进行批量更新时并没有从数据库获取模型。
批量赋值
还可以使用create方法保存一个新的模型。该方法返回被插入的模型实例。但是,在此之前,你需要指定模型的fillable或guarded属性,因为所有Eloquent模型都通过批量赋值(MassAssignment)进行保护,这两个属性分别用于定义哪些模型字段允许批量赋值以及哪些模型字段是受保护的,不能显式进行批量赋值。
当用户通过HTTP请求传递一个不被期望的参数值时就会出现安全隐患,然后该参数以不被期望的方式修改数据库中的字段值。例如,恶意用户通过HTTP请求发送一个is_admin参数,然后该参数映射到模型的create方法,从而允许用户将自己变成管理员。
所以,你应该在模型中定义哪些属性是可以进行赋值的,使用模型上的$fillable属性即可实现。例如,我们设置Flight模型上的name属性可以被赋值:
设置完可以被赋值的属性之后,我们就可以使用create方法在数据库中插入一条新的记录。create方法返回保存后的模型实例:
$flight=App\Flight::create(['name'=>'Flight10']);如果你已经有了一个模型实例,可以使用fill方法通过数组属性来填充:
$flight->fill(['name'=>'Flight22']);黑名单属性
$fillable就像是可以被赋值属性的“白名单”,还可以选择使用$guarded。$guarded属性包含你不想被赋值的属性数组。所以不被包含在其中的属性都是可以被赋值的,因此,$guarded功能就像“黑名单”。当然,这两个属性你只能同时使用其中一个而不能一起使用,因为它们是互斥的。下面的例子中,除了price之外的所有属性都是可以赋值的:
如果你想要让所有属性都是可批量赋值的,可以将$guarded属性设置为空数组:
/** *Theattributesthataren'tmassassignable. * *@vararray */ protected$guarded=[];其它创建方法
firstOrCreate/firstOrNew
还有其它两种可以用来创建模型的方法:firstOrCreate和firstOrNew。firstOrCreate方法先尝试通过给定列/值对在数据库中查找记录,如果没有找到的话则通过给定属性创建一个新的记录。
firstOrNew方法和firstOrCreate方法一样先尝试在数据库中查找匹配的记录,如果没有找到,则返回一个新的模型实例。需要注意的是,通过firstOrNew方法返回的模型实例并没有持久化到数据库中,你还需要调用save方法手动持久化:
//通过属性获取航班,如果不存在则创建... $flight=App\Flight::firstOrCreate(['name'=>'Flight10']); //通过name获取航班,如果不存在则通过name和delayed属性创建... $flight=App\Flight::firstOrCreate( ['name'=>'Flight10'],['delayed'=>1] ); //通过属性获取航班,如果不存在初始化一个新的实例... $flight=App\Flight::firstOrNew(['name'=>'Flight10']); //通过name获取,如果不存在则通过name和delayed属性创建新实例... $flight=App\Flight::firstOrNew( ['name'=>'Flight10'],['delayed'=>1] );updateOrCreate
与此类似的,你还会碰到如果模型已存在则更新,否则创建新模型的场景,Laravel提供了一个updateOrCreate方法来一步完成。和firstOrCreate方法一样,updateOrCreate方法会持久化模型,所以无需调用save():
//如果有从奥克兰到圣地亚哥的航班则将价格设置为$99 //如果没有匹配的模型则创建之 $flight=App\Flight::updateOrCreate( ['departure'=>'Oakland','destination'=>'SanDiego'], ['price'=>99] );删除模型
要删除一个模型,调用模型实例上的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);通过查询删除模型
当然,你还可以通过查询删除多个模型,在本例中,我们删除所有被标记为无效的航班:
$deletedRows=App\Flight::where('active',0)->delete();注:通过Eloquent进行批量删除时,deleting和deleted模型事件在删除模型时不会被触发,这是因为在进行模型删除时不会获取模型。
软删除
除了从数据库物理删除记录外,Eloquent还可以对模型进行“软删除”。当模型被软删除后,它们并没有真的从数据库删除,而是在模型上设置一个deleted_at属性并插入数据库,如果模型有一个非空deleted_at值,那么该模型已经被软删除了。要启用模型的软删除功能,可以使用模型上的Illuminate\Database\Eloquent\SoftDeletestrait并添加deleted_at列到$dates属性:
当然,应该添加deleted_at列到数据表。LaravelSchema构建器包含一个辅助函数来创建该数据列:
Schema::table('flights',function($table){ $table->softDeletes(); });现在,当调用模型的delete方法时,deleted_at列将被设置为当前日期和时间,并且,当查询一个使用软删除的模型时,被软删除的模型将会自动从查询结果中排除。
判断给定模型实例是否被软删除,可以使用trashed方法:
if($flight->trashed()){ // }查询被软删除的模型
包含软删除模型
正如上面提到的,软删除模型将会自动从查询结果中排除,不过,如果你想要软删除模型出现在查询结果中,可以使用withTrashed方法:
$flights=App\Flight::withTrashed() ->where('account_id',1) ->get();withTrashed方法也可以用于关联查询中:
$flight->history()->withTrashed()->get();只获取软删除模型
onlyTrashed方法只获取软删除模型:
$flights=App\Flight::onlyTrashed() ->where('airline_id',1) ->get();恢复软删除模型
有时候你希望恢复一个被软删除的模型,可以使用restore方法:
$flight->restore();你还可以在查询中使用restore方法来快速恢复多个模型,同样,这也不会触发任何模型事件:
App\Flight::withTrashed() ->where('airline_id',1) ->restore();和withTrashed方法一样,restore方法也可以用于关联查询:
$flight->history()->restore();永久删除模型
有时候你真的需要从数据库中删除一个模型,要从数据库中永久删除记录,可以使用forceDelete方法:
//强制删除单个模型实例... $flight->forceDelete(); //强制删除所有关联模型... $flight->history()->forceDelete();查询作用域
全局作用域
全局作用域允许我们为给定模型的所有查询添加条件约束。Laravel自带的软删除功能就使用了全局作用域来从数据库中拉出所有没有被删除的模型。编写自定义的全局作用域可以提供一种方便的、简单的方式来确保给定模型的每个查询都有特定的条件约束。
编写全局作用域
自定义全局作用域很简单,首先定义一个实现Illuminate\Database\Eloquent\Scope接口的类,该接口要求你实现一个方法:apply。需要的话可以在apply方法中添加where条件到查询:
where('age','>',200); } }Laravel应用默认并没有为作用域预定义文件夹,所以你可以按照自己的喜好在app目录下创建Scopes目录。
注:如果你的全局作用域需要添加列到查询的select子句,需要使用addSelect方法来替代select,这样就可以避免已存在的select查询子句造成影响。
应用全局作用域
要将全局作用域应用到模型,需要重写给定模型的boot方法并使用addGlobalScope方法:
添加作用域后,如果使用User::all()查询则会生成如下SQL语句:
select*from`users`where`age`>200匿名的全局作用域
Eloquent还允许我们使用闭包定义全局作用域,这在实现简单作用域的时候特别有用,这样的话,我们就没必要定义一个单独的类了:
where('age','>',200); }); } }移除全局作用域
如果想要在给定查询中移除指定全局作用域,可以使用withoutGlobalScope方法,该方法接收全局作用域的类名作为其唯一参数:
User::withoutGlobalScope(AgeScope::class)->get();或者,如果你使用闭包定义的全局作用域的话:
User::withoutGlobalScope('age')->get();如果你想要移除某几个或全部全局作用域,可以使用withoutGlobalScopes方法:
//移除所有全局作用域 User::withoutGlobalScopes()->get(); //移除某些全局作用域 User::withoutGlobalScopes([FirstScope::class,SecondScope::class])->get();本地作用域
本地作用域允许我们定义通用的约束集合以便在应用中复用。例如,你可能经常需要获取最受欢迎的用户,要定义这样的一个作用域,只需简单在对应Eloquent模型方法前加上一个scope前缀。
作用域总是返回查询构建器实例:
where('votes','>',100); } /** *只包含激活用户的查询作用域 * *@return\Illuminate\Database\Eloquent\Builder */ publicfunctionscopeActive($query) { return$query->where('active',1); } }使用本地作用域
作用域被定义好了之后,就可以在查询模型的时候调用作用域方法,但调用时不需要加上scope前缀,你甚至可以同时调用多个作用域,例如:
$users=App\User::popular()->active()->orderBy('created_at')->get();动态作用域
有时候你可能想要定义一个可以接收参数的作用域,你只需要将额外的参数添加到你的作用域即可。作用域参数应该被定义在$query参数之后:
where('type',$type); } }现在,你可以在调用作用域时传递参数了:
$users=App\User::ofType('admin')->get();比较模型
有时候你可能需要确定两个模型是否是一样的,is方法可用于快速验证两个模型是否有相同的主键、数据表、以及数据库连接:
if($post->is($anotherPost)){ // }事件
Eloquent模型可以触发事件,允许你在模型生命周期中的多个时间点调用如下这些方法:retrieved,creating,created,updating,updated,saving,saved,deleting,deleted,restoring,restored。事件允许你在一个指定模型类每次保存或更新的时候执行代码。
retrieved事件会在从数据库中获取已存在模型时触发。当一个新模型被首次保存的时候,creating和created事件会被触发。如果一个模型已经在数据库中存在并调用save方法,updating/updated事件会被触发,无论是创建还是更新,saving/saved事件都会被触发。
注:通过Eloquent进行批量更新时,模型事件saved和updated不会在更新模型上触发,这是因为这些模型在进行批量更新时没有真正检索过。
举个例子,在Eloquent模型中定义一个$dispatchesEvents属性来映射模型生命周期中多个时间点与对应事件类:
UserSaved::class, 'deleted'=>UserDeleted::class, ]; }观察者
定义观察者
如果你在给定模型中监听多个事件,可以使用观察者来对所有监听器分组到一个类中,观察者类拥有反射你想要监听的Eloquent事件对应的方法名,每个方法接收模型作为唯一参数。Artisan命令make:observer是创建新的观察者类的最简单的方法:
phpartisanmake:observerUserObserver--model=User该命令会将新的观察者生成到App/Observers目录,如果这个目录不存在,Artisan会自动创建。刚创建的观察者类代码如下:
要注册观察者,使用你想要观察模型的observe方法,你可以在某个服务提供者的boot方法中注册观察者,在本例中,我们在AppServiceProvider中注册观察者:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。