# laravel model query 還未看 https://www.cntofu.com/book/107/Laravel%20Database%E2%80%94%E2%80%94Eloquent%20Model%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%EF%BC%88%E4%B8%8B%EF%BC%89.md SQL语句准备阶段主 要是通过SQL语法规则管理器解析SQL语句,这里通过toSql()函数实 现,然后调用语法规则管理器的compileSelect()方法实现SQL语句的解 析 http://semantic-portal.net/laravel-database-query-builder#12 ![](https://i.imgur.com/HaplKeY.png) with vs has vs whereHas https://stackoverflow.com/questions/30231862/laravel-eloquent-has-with-wherehas-what-do-they-mean https://medium.com/johnliu-%E7%9A%84%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E6%80%9D%E7%B6%AD/laravel-18-%E7%A8%AE%E5%84%AA%E5%8C%96%E8%B3%87%E6%96%99%E6%9F%A5%E8%A9%A2%E6%95%88%E8%83%BD%E7%9A%84%E6%96%B9%E5%BC%8F-7b3d17886a62 底層(非常重要 每次近來都看一下) 必看 https://learnku.com/articles/13106/laravel-source-code-reading-guide-model-additions-and-deletions-to-check-the-underlying-code-implementation ORM builder解析 https://zhuanlan.zhihu.com/p/75433003 https://cloud.tencent.com/developer/article/1520145 ![](https://i.imgur.com/8dleG48.png) ## 兩點距離 ![](https://i.imgur.com/do4qcdW.png) ## each limit https://learnku.com/articles/24787 ## throght A hasMany/hasOne B, B hasMany/hasOne C, then A hasManyThrough C (through B) ## between 如果where date 要小心 https://www.youtube.com/watch?v=bJkEBTg1NGA&ab_channel=Laratips ## get抓relation get 去抓Relation 如果已經load 就抓 沒的話跑relation getResult https://learnku.com/articles/9906/implementation-of-dynamic-properties-of-laravel ## 排序空的欄位 要注意 orderBy會將空的一樣牌上面 如何讓Null 排序總是最後 https://www.youtube.com/watch?v=vPsLTuBdCek&ab_channel=Laratips ## withCast ![](https://i.imgur.com/VTDKKJ2.png) ![Uploading file..._f4klquu6c]() ## has whereHas 可去看底層 whereHas就是has ## morph查詢 ![](https://i.imgur.com/AgdcfnZ.png) ## 高階用法 ![](https://i.imgur.com/ImAfHwy.png) orWhere限定 可去看__get ## laravel subquery https://stackoverflow.com/questions/16815551/how-to-do-this-in-laravel-subquery-where-in ## 關聯表的欄位 https://learnku.com/articles/54230 ## 指定日期 ![](https://i.imgur.com/fbs0adu.png) ## selectRaw selectRaw = addSelect(DB::raw) 所以不用怕它蓋過 ![](https://i.imgur.com/RuthW61.png) ## orderBy like https://yiyingloveart.blogspot.com/2016/05/larave-orderbyraw.html ## orderby 指定順序  未開始 已開始 .. ![](https://i.imgur.com/8kt9zwP.png) ## whereRaw vs Where(DB::raw()) https://learnku.com/articles/19252 ## load vs with https://www.amitmerchant.com/Laravel-Eager-Loading-Load-Vs-With/ ## update DB::raw ![](https://i.imgur.com/GiemWmL.png) ## whereColumn ![](https://i.imgur.com/8FoyxhI.png) ## use relation column orderBy ![](https://i.imgur.com/rvVVb5H.png) https://www.youtube.com/watch?v=lRi1-RYnQ7A&ab_channel=LaravelDaily ![](https://i.imgur.com/LhJuuve.png) ## sql 函數 ![](https://i.imgur.com/ucm9tZ8.png) ## wasRecentlyCreated ![](https://i.imgur.com/vqO2dwW.png) https://www.youtube.com/watch?v=w7x76bpp0QE ## 子查詢 selectSub ## DB:select and DB:raw https://fideloper.com/laravel-raw-queries namespace Illuminate\Database\Eloquent; abstract class Model implements ... { public function __call($method, $parameters) { if (in_array($method, ['increment', 'decrement'])) { return $this->$method(...$parameters); } return $this->newQuery()->$method(...$parameters); } public static function __callStatic($method, $parameters) { return (new static)->$method(...$parameters); } // new Eloquent Builder public function newQuery() { return $this->registerGlobalScopes($this->newQueryWithoutScopes()); } public function newQueryWithoutScopes() { $builder = $this->newEloquentBuilder($this->newBaseQueryBuilder()); //设置builder的Model实例,这样在构建和执行query时就能使用model中的信息了 return $builder->setModel($this) ->with($this->with) ->withCount($this->withCount); } //创建数据库连接的QueryBuilder protected function newBaseQueryBuilder() { $connection = $this->getConnection(); return new QueryBuilder( $connection, $connection->getQueryGrammar(), $connection->getPostProcessor() ); } } ## upsert upsert 方法来表示多个更新或创建操作。它是这样使用的: ``` Flight::upsert([ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' =>99], ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] ], ['departure', 'destination'], ['price'] ); ``` 这有点复杂: 第一个数组:要插入或更新的值 第二:select 语句中使用的唯一标识符字段 第三:如果记录存在则会更新的字段 所以这个例子将会: 插入或更新从 Oakland 到 San Diego 的航班,价格为 99 插入或更新从 Chicago 到 New York 的航班,价格为 150 ## many 抓取最新最就或自訂 ### oldestOfMany 有一种特殊的关系叫做 oldestOfMany 。如果您经常需要 hasMany 关系中最旧的模型,则可以使用它。 在此示例中,我们有一个 Employee 和一个 Paycheck 模型。 一对多关系: ``` class Employee extends Models { public function paychecks() { return $this->hasMany(Paycheck::class); } } ``` 想要获得最早的 Paycheck ,不必每次都编写自定义查询,可以为它创建一个关系。 ``` class Employee extends Models { public function oldestPaycheck() { return $this->hasOne(Paycheck::class)->oldestOfMany(); } } ``` 在这种情况下,必须使用 hasOne 方法,因为它只会返回一个 Paycheck 模型,即最早的那条。获取的是 Paycheck 是最小的自增 ID 。因此,如果使用 UUID 作为外键,它将无法正常工作。 ### latestOfMany ``` 与 Oldestofmany 类似,我们也可以使用 Newestofmany: class Employee extends Models { public function latestPaycheck() { return $this->hasOne(Paycheck::class)->latestOfMany(); } } ``` 最新的 paycheck 自增 ID 最大的那一条数据。 ### ofMany 您还可以将 ofMany 关系与一些自定义逻辑结合使用,例如: ``` class User extends Authenticable { public function mostPopularPost() { return $this->hasOne(Post::class)->ofMany('like_count', 'max'); } } ``` 这将返回 like_count 最高的 post 实例。 因此,您可以使用以下关系来代替每次编写自定义查询: ``` oldestOfMany latestOfMany ofMany ``` ## 懒加载特定字段 select * 查询可能会很慢并且很消耗内存。 如果您想建立关系但不需要每一列,你可以指定要加载哪些列: `Product::with('category:id,name')->get();` 在这种情况下,Eloquent 将运行 select id, name from categories 查询。 ## call 使用到Macroable 跟反射 https://learnku.com/articles/35970 ## get relation 沒有with 一開始的情況 https://learnku.com/articles/6447/laravel-database-eloquent-model-model-association-initialization-source-code-analysis ## 多態with ![](https://i.imgur.com/NofIsyl.png) 往裡面放 morphableEagerLoads 保护数组 $morphableEagerLoads 为每个单独的形态类型加载关系图。 ## 當你要 first存在就刪除 或新增 不是在first 然後if isset delete 在create 直接 `$this->service->updateOrCreate(['id' => optional($termOfService)->id], $request->validated());` ![](https://i.imgur.com/YjGr9TR.png) 注意 這只能用在不可null的欄位 因為where如果你搜尋null會幫你去whereNull去找 ## tobase https://www.youtube.com/watch?v=sE4E5j32VoE 出來是原始數據 不是model 只要顯示資料很方便 大量的前提 ## 8.23 sole() 可以丟不同錯誤的 https://laravel-news.com/understanding-the-sole-query-builder-method ## 可以直接table的原因 Eloquent ORM既可以通过静态调用执行方法,也可以先获取到模型对象,然后执行方法。但他们实质是一样的。在Model中定义的静态方法如下: ``` protected static function boot() protected static function bootTraits() public static function clearBootedModels() public static function on($connection = null) public static function onWriteConnection() public static function all($columns = ['*']) public static function with($relations) public static function destroy($ids) public static function query() public static function resolveConnection($connection = null) public static function getConnectionResolver() public static function setConnectionResolver(Resolver $resolver) public static function unsetConnectionResolver() public static function __callStatic($method, $parameters) ``` 可以看到,形如User::find(1)/User::where()的静态调用方法,本身不在类中有定义,而是转发到__callStatic魔术方法: ``` public static function __callStatic($method, $parameters) { return (new static)->$method(...$parameters); } ``` 也就是先实例化自身,然后在对象上执行调用。所以,在使用Eloquent的过程中,模型基本上都会有实例化的过程 ## model 建構式 Model的构造方法中,都做了哪些动作: ``` public function __construct(array $attributes = []) { $this->bootIfNotBooted(); $this->syncOriginal(); $this->fill($attributes); } ``` `bootIfNotBooted()`是模型的启动方法,标记模型被启动,并且触发模型启动的前置与后置事件。在启动过程中,会查询模型使用的trait中是否包含boot{Name}形式的方法,有的话就执行,这个步骤可以为模型扩展一些功能,比如文档中的软删除: 要在模型上启动软删除,则必须在模型上使用 `Illuminate\Database\Eloquent\SoftDeletes` trait 并添加 deleted_at 字段到你的 $dates 属性上。 就是在启动SoftDeletestraits的时候,给模型添加了一组查询作用域,来新增Restore()/WithTrashed()/WithoutTrashed()/OnlyTrashed()四个方法,同时改写delete方法的逻辑,从而定义了软删除的相关行为。 syncOriginal()方法的作用在于保存原始对象数据,当更新对象的属性时,可以进行脏检查。 fill($attributes)就是初始化模型的属性。 在实际运用中可能会注意到,我们很少会用new的方法、通过构造函数来实例化模型对象,但在后续我们要说道的查询方法中,会有一个装载对象的过程,有这样的用法。为什么我们很少会new一个Model,其实原因两个方面:首先从逻辑上说,是先有一条数据库记录,然后才有基于该记录的数据模型,所以在new之前必然要有查询数据库的动作;其次是因为直接new出来的Model,它的状态有可能并不正确,需要手动进行设置,可以查阅Model的newInstance()/newFromBuilder()两个方法来理解“状态不正确”的含义。 ## 深入 - Eloquent ORM的查询过程 我们以User::all()的查询过程来作为本小节的开始,Model的all()方法代码如下: ``` public static function all($columns = ['*']) { return (new static)->newQuery()->get( is_array($columns) ? $columns : func_get_args() ); } ``` new static: 模型实例化,得到模型对象。 $model->newQuery(): 根据模型对象,获取查询构造器$query。 $query->get($columns): 根据查询构造器,取得模型数据集合。 Eloquent ORM的查询过程,就可以归纳成这三个过程: [模型对象]=>[查询构造器]=>[数据集合] ## whereHas用到父表欄位 ![](https://i.imgur.com/mCL6GwN.png) ## newQuery newQuery() -->newQueryWithoutScopes() // 添加查询作用域 -->newModelQuery() // 添加对关系模型的加载 -->newEloquentBuilder(newBaseQueryBuilder()) // 获取查询构造器 --> return new Builder(new QueryBuilder()) // 查询构造器的再封装 first不會吃到 first是 take 1 get get 函数会将 QueryBuilder 所获取的数据进一步包装 hydrate。hydrate 函数会将数据库取回来的数据打包成数据库模型对象 Eloquent Model,如果可以获取到数据,还会利用函数 eagerLoadRelations 来预加载关系模型。 再轉發到 ``` public function __call($method, $parameters) { // "如果是這兩個方法的話會優先調用 Model自身定義的" if (in_array($method, ['increment', 'decrement'])) { return $this->$method(...$parameters); } // "這裡的 $this->newQuery() 就是 Eloquent Builder 對象!" // "轉發調用,實際執行了 $this->newQuery()->{$method}" return $this->forwardCallTo($this->newQuery(), $method, $parameters); } ``` 所以model本身有的 加上first ## where where用法的一些注意事项: where的第一个参数是数组或闭包时,条件被描述为Nested类型,也就是参数分组。(or and 的感覺) where的第二个参数,比较符号是等于号时,可以省略。 where的第三个参数是闭包时,表示一个子查询 ## 关系模型 关于关系模型的定义,其操作接口全部定义在Illuminate\Database\Eloquent\Concerns\HasRelationships::trait中。每个关系定义方法,都是对一个关系对象的定义。 关系对象全部继承自 Illuminate\Database\Eloquent\Relations\Relation::abstract 虚拟类。关系对象由一个查询构造器组成,用来保存由关系定义所决定的关系查询条件,和加载关系时的额外条件。 比如一对一(多)的关系定义中: ``` public function addConstraints() { if (static::$constraints) { $this->query->where($this->foreignKey, '=', $this->getParentKey()); $this->query->whereNotNull($this->foreignKey); } } ``` 每当需要获取关系数据时,都会实例化关系对象,实例化的过程中调用addConstraints方法。与此同时,在加载关系数据时,可以传入额外的查询条件: ``` $users = App\User::with(['posts' => function ($query) { $query->where('title', 'like', '%first%'); }])->get(); ``` 这些条件最终都会保存在关系对象的查询构造器中,在获取关系数据时,起到筛选作用。 ## Laravel源码分析之模型关联 https://cloud.tencent.com/developer/article/1520141 ## 动态属性加载关联模型 上面我们定义了三种使用频次比较高的模型关联,下面我们再来看一下在使用它们时关联模型时如何加载出来的。我们可以像访问属性一样访问定义好的关联的模型,例如,我们刚刚的 User 和 Post 模型例子中,我们可以这样访问用户的所有文章: ``` $user = App\User::find(1); foreach ($user->posts as $post) { // } ``` 还记得我们上一篇文章里讲获取模型的属性时提到过的吗? “如果模型的 $attributes属性里没有这个字段,那么会尝试获取模型关联的值”: ``` abstract class Model implements ... { public function __get($key) { return $this->getAttribute($key); } public function getAttribute($key) { if (! $key) { return; } //如果attributes数组的index里有$key或者$key对应一个属性访问器`'get' . $key` 则从这里取出$key对应的值 //否则就尝试去获取模型关联的值 if (array_key_exists($key, $this->attributes) || $this->hasGetMutator($key)) { return $this->getAttributeValue($key); } if (method_exists(self::class, $key)) { return; } //获取模型关联的值 return $this->getRelationValue($key); } public function getRelationValue($key) { //取出已经加载的关联中,避免重复获取模型关联数据 if ($this->relationLoaded($key)) { return $this->relations[$key]; } // 调用我们定义的模型关联 $key 为posts if (method_exists($this, $key)) { return $this->getRelationshipFromMethod($key); } } protected function getRelationshipFromMethod($method) { $relation = $this->$method(); if (! $relation instanceof Relation) { throw new LogicException(get_class($this).'::'.$method.' must return a relationship instance.'); } //通过getResults方法获取数据,并缓存到$relations数组中去 return tap($relation->getResults(), function ($results) use ($method) { $this->setRelation($method, $results); }); } } ``` 在通过动态属性获取模型关联的值时,会调用与属性名相同的关联方法,拿到关联实例后会去调用关联实例的 getResults方法返回关联的模型数据。 getResults也是每个Relation子类需要实现的方法,这样每种关联都可以根据自己情况去执行查询获取关联模型,现在这个例子用的是一对多关联 在 hasMany类中我们可以看到这个方法的定义如下: ``` class HasMany extends HasOneOrMany { public function getResults() { return $this->query->get(); } } ``` ``` class BelongsToMany extends Relation { public function getResults() { return $this->get(); } ``` public function get($columns = ['*']) { $columns = $this->query->getQuery()->columns ? [] : $columns; $builder = $this->query->applyScopes(); $models = $builder->addSelect( $this->shouldSelect($columns) )->getModels(); $this->hydratePivotRelation($models); if (count($models) > 0) { $models = $builder->eagerLoadRelations($models); } return $this->related->newCollection($models); } } ## with分析 https://ithelp.ithome.com.tw/articles/10193034 ## 关联方法 出了用动态属性加载关联数据外还可以在定义关联方法的基础上再给关联的子模型添加更多的where条件等的约束,比如: `$user->posts()->where('created_at', ">", "2018-01-01");` Relation实例会将这些调用通过 __call转发给子模型的Eloquent Builder去执行 Relation实例会将这些调用通过 __call转发给子模型的Eloquent Builder去执行。 ``` abstract class Relation { /** * Handle dynamic method calls to the relationship. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); } $result = $this->query->{$method}(...$parameters); if ($result === $this->query) { return $this; } return $result; } } ``` hasMany 之類的都是繼承relation 所以都會用到這個 基本上都沒hasMacro 不會特別擴張 所以就等於下面 ->query去找對應的builder ## 便捷获取一对多关系中子集的数量 除了可以使用 Eloquent 中的 withCount() 方法统计子集数量外,还可以直接用 loadCount() 更加便捷和快速获取: ``` // if your Book hasMany Reviews... $book = App\Book::first(); $book->loadCount('reviews'); // 使用 $book->reviews_count 获取 Reviews 数量; // 还可以在 loadCount 中添加额外的查询条件 $book->loadCount(['reviews' => function ($query) { $query->where('rating', 5); }]); ``` ## 提取一个重复回调作为变量 如果你有一个重复使用的回调函数,你可以提取它作为变量。 // 你有一个很长的包含重复的回调函数 ``` $results = Model::with('relationships') ->whereHas('relationships', function($query) use ($var1, $var2) { return $query->where('field1', $var1)->where('field2', $var2); }) ->withCount('relationships', function($query) use ($var1, $var2) { return $query->where('field1', $var1)->where('field2', $var2); }) ->get(); // 你可以提取它作为变量 $callback = function($query) use ($var1, $var2) { return $query->where('field1', $var1)->where('field2', $var2); }); $results = Model::with('relationships') ->whereHas('relationships', $callback) ->withCount('relationships', $callback) ->get(); ``` ## 新的 Eloquent 查询构建器方法 whereBelongsTo() Laravel 8.63.0 版本带有一个新的 Eloquent 查询构建器方法 whereBelongsTo() 。 这允许你从你的查询中删除 BelongsTo 外键名称,并使用关联方法替代(该方法会根据类名自动确定关联与外键,也可以添加第二个参数手动关联)。 ``` // 以前: $query->where('author_id', $author->id) // 现在: $query->whereBelongsTo($author) // 轻松添加更多的过滤功能: Post::query() ->whereBelongsTo($author) ->whereBelongsTo($cateogry) ->whereBelongsTo($section) ->get(); // 指定一个自定义的关系: $query->whereBelongsTo($author, 'author') ``` ## whereHas 的一个更简短的方法 在 Laravel 8.57 中发布:通过包含一个简单条件的简短方法来写 whereHas()。 ``` // 以前 User::whereHas('posts', function ($query) { $query->where('published_at', '>', now()); })->get(); // 现在 User::whereRelation('posts', 'published_at', '>', now())->get(); ``` ## with 指定的selct 渴求式加载特定字段 你可以在 Laravel 中渴求式加载并指定关联中的特定字段。 ``` $users = App\Book::with('author:id,name')->get(); 你同样可以在深层级中这样做,如第二层级关系: $users = App\Book::with('author.country:id,name')->get(); ``` ## 数据库原始查询计算运行得更快 使用类似 whereRaw() 方法的 SQL 原始查询,直接在查询中进行一些特定于数据库的计算,而不是在 Laravel 中,通常结果会更快。 例如,如果您想获得注册后 30 天以上仍处于活跃状态的用户,请使用以下代码: ``` User::where('active', 1) ->whereRaw('TIMESTAMPDIFF(DAY, created_at, updated_at) > ?', 30) ->get(); ``` ## 制作模型的副本 如果你有两个非常相似的模型(比如送货地址和账单地址),而且你想要复制其中一个作为另一个,你可以使用 replicate() 方法并更改一部分属性。 官方文档 的示例: ``` $shipping = Address::create([ 'type' => 'shipping', 'line_1' => '123 Example Street', 'city' => 'Victorville', 'state' => 'CA', 'postcode' => '90001', ]); $billing = $shipping->replicate()->fill([ 'type' => 'billing' ]); $billing->save(); ``` ## 失败时执行任何操作# 当查询一条记录时,如果没有找到,你可能想执行一些操作。除了用 ``` ->firstOrFail() 会抛出 404 之外,你可以在失败时执行任何操作,只需要使用 ->firstOr(function() { ... }) $model = Flight::where('legs', '>', 3)->firstOr(function () { // ... }) ``` ## updateOrcreate取代 ![](https://i.imgur.com/roFFegz.png) 之後新版本的用upsert 不用每次foreach裡面都要updateOrCreate 但他檢查的要uninqu欄位 https://www.youtube.com/watch?v=J8x268as2qo ## 一些遇到的 ![](https://i.imgur.com/onPxH3K.png) 第一 sql 不分大小寫 所以sum SUM都可以 第二 select最後一個不要加,會出錯 第三 groupBy rollup 要這樣寫 ## toSql注意事項 with算不同的關聯 所以他不會toSql ## db table 原理 ![](https://i.imgur.com/aHAlhPg.png) 使用 ![](https://i.imgur.com/3Ymw23B.png) ![](https://i.imgur.com/hJUWHw5.png) ## where update https://stackoverflow.com/questions/27248753/laravel-update-query/27248908 在sql update ![](https://i.imgur.com/JMzzJ3N.png) update 函数 save 函数仅仅支持手动的属性赋值,无法批量赋值。laravel 的 Eloquent Model 还有一个函数: update 支持批量属性赋值。有意思的是,Eloquent Builder 也有函数 update,那个是上一小节提到的 performUpdate 所调用的函数。 两个 update 功能一致,只是 Model 的 update 函数比较适用于更新从数据库取回的数据库对象: ``` $flight = App\Flight::find(1); $flight->update(['name' => 'New Flight Name','desc' => 'test']); ``` 而 Builder 的 update 适用于多查询条件下的更新: ``` App\Flight::where('active', 1) ->where('destination', 'San Diego') ->update(['delayed' => 1]); ``` ## make 函数 同样的,save 的插入也仅仅支持手动属性赋值,如果想实现批量属性赋值的插入可以使用 make 函数: ``` $model = App\Flight::make(['name' => 'New Flight Name','desc' => 'test']); $model->save(); ``` make 函数实际上仅仅是新建了一个 Eloquent Model,并批量赋予属性值: ``` public function make(array $attributes = []) { return $this->newModelInstance($attributes); } public function newModelInstance($attributes = []) { return $this->model->newInstance($attributes)->setConnection( $this->query->getConnection()->getName() ); } ``` ## select的語法 能用的 ![](https://i.imgur.com/6xS97ch.png) ## sql select查询语句会构造select片段,即通过 compileColumns()函数实现“select $columns”部分,而$columns为默认 值“*”,从而实现select查询语句中的“select *”部分,接着构造from片 段,即通过compileFrom()函数实现查询语句中的“from $table”部分,这 里实现为“from users”,最后通过concatenate()函数将两个查询语句片段 组装成一个查询语句返回,即生成SQL语句“select * from users”。 ## .SQL语句执行阶段 ## 操作 ![](https://i.imgur.com/LXYThUF.png) ### 新增 ![](https://i.imgur.com/TMIlvWl.png) ### 刪除 ![](https://i.imgur.com/S9LYhON.png) ### 修改 ![](https://i.imgur.com/ncbelgJ.png) ## 原始表達式 raw 有些時候你需要使用raw expression在查詢語劇裡,這樣的表達式會成為字串插入至查詢,因此要小心勿建立任何SQL隱碼攻擊點。要建立raw expression,你可以使用DB::raw方法: 使用`Raw expression` ``` $users = DB :: table ( 'users' ) ->選擇( DB :: raw ( 'count(*) as user_count, status' )) -> where ( 'status' , '<>' , 1 ) -> groupBy ( '狀態' ) ->獲取(); ``` 衍伸範例 `-> where ( DB :: raw ( 'IFNULL(advertsolution_m.start_date,1911/01/01)''), '<=' , DATE_FORMAT ( $now , 'Y/m/d' ))` 另外的用法 `->whereRaw('裡面放sql的語法字串')` 以上這例子是說當你advertsolution_m.start_date的值為null的時後給它ㄧ個預設值1911/01/01,因為Laravel沒有這類的功能,所以我們可以用DB::raw。 注意注意 ![](https://i.imgur.com/qPhRWNT.png) raw 也可以用PDO寫法 用? 去注入value ## join 跟關聯過去 這是又琳說的 我覺得有道理 關聯是父子 join 不管是不是父子都可以 ORM用意就是不要用join 取代掉了 所以能用ORM做法就用 不能再join 同層就用join 另外left join可能會有重複(小心) 另外再 ## 查詢建構的 包含left join相關的 https://stackoverflow.com/questions/55713709/how-laravel-calls-a-method-that-is-not-in-class-and-parrent model->leftJoin 之類的原因 在於你model調用call 等於Elo Builder那邊調用call 然後他找不到會往下傳 Elp那邊找不到也會去調用Call 所以你要找方法其實可以去 query builder 然後 callstaic基本會再呼叫call ## insert跟 ORM的create實際應用差別 上面for一次大量的 跟下面一次插入大量 下面比較節省效能 ![](https://i.imgur.com/0TJUwYp.png) ![](https://i.imgur.com/qi3arFs.png) ## selectRaw vs Raw https://stackoverflow.com/questions/34408900/laravel-selectraw-vs-dbraw https://stackoverflow.com/questions/50398877/laravel-query-builder-selectraw-or-select-and-raw ## ORM 底層 了Active Record模式与数据库进行交互,使得操作数据库变得 极为简单。这里先从概念上理解Eloquent ORM是什么。ORM英文是 Object Relational Mapping(对象关系映射),这是一种面向对象编程中 用于解决不同系统间数据转换的方案,相当于创建了一个 “虚拟对象数 据库”,通俗的理解就是将数据库中复杂的结构封装成更加容易使用的 接口提供给用户。 **对比两者的区别来理解**Active Record模式的思 想。在查询构造器中访问数据表时需要通过table()函数给定表名,而在 Eloquent ORM中每个类就对应一个数据表;查询构造器查询结果返回的 是数据数组,而在Eloquent ORM中返回的则是类的实例对象,每个实例 对象对应一行数据;在Eloquent ORM中类直接封装了对该数据表的操 作,使用时更加方便。 ## ORM 整理 https://zhuanlan.zhihu.com/p/75433003 在建構式那邊 ``` public function __construct(array $attributes = []) { $this->bootIfNotBooted(); $this->syncOriginal(); $this->fill($attributes); } ``` bootIfNotBooted()是模型的启动方法,标记模型被启动,并且触发模型启动的前置与后置事件。在启动过程中,会查询模型使用的trait中是否包含boot{Name}形式的方法,有的话就执行,这个步骤可以为模型扩展一些功能,比如文档中的软删除: 可看軟刪除那邊寫的 syncOriginal()方法的作用在于保存原始对象数据,当更新对象的属性时,可以进行脏检查。 fill($attributes)就是初始化模型的属性。 重點 在实际运用中可能会注意到,我们很少会用new的方法、通过构造函数来实例化模型对象,但在后续我们要说道的查询方法中,会有一个装载对象的过程,有这样的用法。为什么我们很少会new一个Model,其实原因两个方面:首先从逻辑上说,是先有一条数据库记录,然后才有基于该记录的数据模型,所以在new之前必然要有查询数据库的动作;其次是因为直接new出来的Model,它的状态有可能并不正确,需要手动进行设置,可以查阅Model的newInstance()/newFromBuilder()两个方法来理解“状态不正确”的含义。 ### 深入 - Eloquent ORM的查询过程 https://zhuanlan.zhihu.com/p/75433003 ``` public static function all($columns = ['*']) { return (new static)->newQuery()-> ( is_array($columns) ? $columns : func_get_args() ); } ``` 这个查询过程,可以分成三个步骤来执行: new static: 模型实例化,得到模型对象。 $model->newQuery(): 根据模型对象,获取查询构造器$query。 $query->get($columns): 根据查询构造器,取得模型数据集合。 Eloquent ORM的查询过程,就可以归纳成这三个过程: **[模型对象]=>[查询构造器]=>[数据集合]** 数据集合也是模型对象的集合,即使是做first()查询,也是先获取到只有一个对象的数据集合,然后取出第一个对象。但数据集合中的模型对象,与第一步中的模型对象不是同一个对象,作用也不一样。第一步实例化得到的模型对象,是一个**空对象**,其作用是为了获取第二步的查询构造器,第三步中的模型对象,是经过数据库查询,获取到数据后,对数据进行封装后的对象,是一个有数据的对象,从查询数据到模型对象的过程,我称之为装载对象,**装载对象**,正是使用的上文提及的``方newFromBuilder()``方法。 ## ORM 細節 ![](https://i.imgur.com/BXLSD9C.png) ![](https://i.imgur.com/EWl6owx.png) ![](https://i.imgur.com/3vHfwtH.png) ![](https://i.imgur.com/Q9opDOp.png) Eloquent ORM的底层使用了查询构造器来实现,这 里通过newBaseQueryBuilder()函数创建一个基础查询构造器,而这个基 础查询构造器中的数据库连接最终也是通过数据库控制器 (Illuminate\Database\DatabaseManager类实例)的connection()函数实现 的。同时,SQL语法规则实例和结果处理实例与上一小节中介绍的也是 相同的,所以对于这个基础查询构造器无论是对应的类还是内部封装的 相关属性都与上一小节介绍的查询构造器相同。在完成基础查询构造器 的实例化后,通过newEloquentBuilder()函数对该查询构造器进行封装, 实现Eloquent查询构造器的创建 ## ORM 基本做完 buider 接下就是要封裝模型 ![](https://i.imgur.com/7hKSvgm.png) Eloquent ORM与上一节查询构造 器操作数据库不同的是,这里还需要封装其他部分,包括模型类实例和 数据表名称等,主要是需要封装模型类实例,因为对数据库的操作不再 以数组的形式交互,而是通过这个模型类实例来与数据库进行交互。数 据表名称是通过Model类对象的getTable()函数获取的,如果对象中的 $table属性存在值,则使用该数据表名,如果不存在,则以模型类名的 复数作为数据表名,即通过代码 ``` “str_replace('\\', '', Str::snake(Str::plural(class_basename($this)))); ``` ”实现,这就是为什么创建 的模型类没有指定数据表名,依然可以操作数据库中的数据表的原因, 在本实例中,默认的数据表名为“users”。 ## ORM 封裝模型 ![](https://i.imgur.com/lWa3x2F.png) ![](https://i.imgur.com/1o6ZSXh.png) hydrate ![](https://i.imgur.com/r4yeAaE.png) ![](https://i.imgur.com/KDfqpkU.png) ![](https://i.imgur.com/H45rkIy.png) 获取查询数据后将对数据进行封装,通过array_map()函数对数组中 的每项进行处理,处理函数为一个匿名函数,该匿名函数中调用 newFromBuilder()函数进行数据处理,该函数通过newInstance()函数实例 化一个Model类,并将数据赋值给该实例的$attributes属性。接着实例化 一个Illuminate\Database\Eloquent\Collection类实例,该类继承自集合类 Illuminate\Support\Collection,并将array_map()函数生成的模型类实例数 组传递给集合类构造函数,最终将查询获取的数据以Eloquent集合的形 式进行封装,而Eloquent集合中存储的是一个模型类实例数组,每一个 模型类实例对应查询的一条结果 ## ORM的使用 Eloquent ORM的使用,首先需要构建数据表并添加相应的 数据。要完成数据表的创建和数据填充一般需要三个步骤:一是在数据 库中创建对应的数据表,二是对应每个数据表创建模型类文件,三是完 成数据库的填充 ### 刪除 过delete()方法或destroy()方法实现,其中delete()方 法为实例方法,需要查询到相应的数据并通过模型实例调用,而 destroy()方法可以直接调用,通过索引删除记录。 ![](https://i.imgur.com/aqXHwsA.png) ### newQuery newQuery()的调用过程很长,概括如下: newQuery() -->newQueryWithoutScopes() // 添加查询作用域 -->newModelQuery() // 添加对关系模型的加载 -->newEloquentBuilder(newBaseQueryBuilder()) // 获取查询构造器 --> return new Builder(new QueryBuilder()) // 查询构造器的再封装 ## builder 差別 数据库查询构造器Builder定义了一组通用的,人性化的操作接口,来描述将要执行的SQL语句(见官方文档【数据库 - 查询构造器】一章。)在这一层提供的接口更接近SQL原生的使用方法,比如:where/join/select/insert/delete/update等,都很容易在数据库的体系内找到相应的操作或指令;EloquentBuilder是对Builder的再封装,EloquentBuilder在Builder的基础之上,定义了一些更复杂,但更便捷的描述接口(见官方文档【Eloquent ORM - 快速入门】一章。),比如:first/firstOrCreate/paginator等。 ### 3.1 EloquentBuilder EloquentBuilder是Eloquent ORM查询构造器,是比较高级的能与数据库交互的对象。一般在Model层面的与数据库交互的方法,都会转发到Model的EloquentBuilder对象上去执行,通过下列方法可以获取到一个Eloquent对象: ``` $query = User::query(); $query = User::select(); ``` 每个EloquentBuilder对象都会有一个Builder成员对象。 ### 3.2 Builder Builder是数据库查询构造器,在Builder层面已经可以与数据库进行交互了,如何获取到一个Builder对象呢?下面展示两种方法: // 获取Builder对象 ``` $query = DB::table('user'); $query = User::query()->getQuery(); ``` // Builder对象与数据库交互 $query->select('name')->where('status', 1)->orderBy('id')- ## ConnectionInterface ConnectionInterface对象是执行SQL语句、对读写分离连接进行管理的对象,也就是数据库连接对象。是最初级的、能与数据交互的对象: ``` DB::select('select * from users where active = ?', [1]); DB::insert('insert into users (id, name) values (?, ?)', [1, 'Dayle']); ``` 虽然DB门面指向的是Illuminate\Database\DatabaseManager的实例,但是对数据库交互上的操作,都会转发到connection上去执行。 DatabaseServiceProvider的启动方法中执行的代码 Model::setConnectionResolver($this->app['db']); ,这个步骤就是为了后续获取Builder的第一个成员对象ConnectionInterface,数据库连接对象。前文提到过,数据库的连接并不是在服务提供者启动时进行的,是在做出查询动作时才会连接数据库: ``` // Illuminate\Database\Eloquent\Model::class protected function newBaseQueryBuilder() { // 获取数据库连接 $connection = $this->getConnection(); // Builder实例化时传入的三个对象,ConnectionInterface、Grammar、Processor return new QueryBuilder( $connection, $connection->getQueryGrammar(), $connection->getPostProcessor() ); } public function getConnection() { return static::resolveConnection($this->getConnectionName()); } public static function resolveConnection($connection = null) { // 使用通过Model::setConnectionResolver($this->app['db'])注入的resolver进行数据库的连接 return static::$resolver->connection($connection); } ``` ### Grammar Grammar对象是SQL语法解析对象,我们在Builder对象中调用的方法,会以Builder属性的形式将调用参数管理起来,然后在调用SQL执行方法时,先通过Grammar对象对这些数据进行解析,解析出将要执行的SQL语句,然后交给ConnectionInterface执行,获取到数据。 ### Processor Processor对象的作用比较简单,将查询结果数据返回给Builder,包括查询的行数据,插入后的自增ID值。 ## 為什麼可以抓取database屬性 https://devindeving.blogspot.com/2020/12/php-magic-method-get-set-and-laravel.html Laravel中的應用 其實__get、__set在Laravel中,有相當廣泛的用運用,而其中有一個應用是,大家基本上都會用到的,那就是Model。 不知道大家有沒有好奇過,為什麼Laravel中的Eloquent Model,可以的取得db中任意欄位的值?為什麼不需要在Model中做事先的宣告? $user = User::first(); $user->email; 像這個範例,你並不用手動去User Model的類別新增email屬性,就可以直接透過user model物件取得email。 其實這就是__get、__set在其中搞怪。 如果我們把Laravel Model的程式找出來,會發現這段: `// 路徑:/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php` ``` /** * Dynamically retrieve attributes on the model. * * @param string $key * @return mixed */ public function __get($key) { return $this->getAttribute($key); } /** * Dynamically set attributes on the model. * * @param string $key * @param mixed $value * @return void */ public function __set($key, $value) { $this->setAttribute($key, $value); } ``` 就會發現Laravel其實也是透過魔術方法,去幫你取得各種屬性的! 當我們要讀取email時,因為user model中並不存在這個屬性,因此會觸發__get,再藉由getAttribute方法,去找出真正的值。 ## get get 方法返回 Model 实例集合时我们看到过把数据记录的字段和字段值都赋值给了 Model 实例的 $attributes 属性,Model 实例访问和设置这些字段对应的属性时是通过__get 和__set 魔术方法动态获取和设置这些属性值的。 所以你也可以選擇$attributes的要拿多少 get 函数会将 QueryBuilder 所获取的数据进一步包装 hydrate。hydrate 函数会将数据库取回来的数据打包成数据库模型对象 Eloquent Model,如果可以获取到数据,还会利用函数 eagerLoadRelations 来预加载关系模型。 所以像是first 是用take去改變屬性 然後get的時候會觸發 他不是take會去變成sql ## take陷阱 如果你用take 雖然他是limnt 但他不會去newQuery 他是改變屬性然後等get的時候再改變 ## Eloquent Builder and Query Builder Eloquent Builder 与底层数据库交互的部分都是依赖 Query Builder 来实现的 Eloquent Builder 的 where 方法在接到调用请求后直接把请求转给来 Query Builder 的 where 方法,然后 get 方法也是先通过 Query Builder 的 get 方法执行查询拿到结果数组后再通过 newFromBuilder 方法把结果数组转换成 Model 对象构成的集合 https://learnku.com/articles/13106/laravel-source-code-reading-guide-model-additions-and-deletions-to-check-the-underlying-code-implementation ## create () 和 save () 方法的底层原理是什么? https://learnku.com/laravel/t/21670 ## 檢查屬性更改 https://www.youtube.com/watch?v=_xluet13xxE https://laravel.com/docs/master/eloquent#updates Eloquent 提供了isDirty、isClean和wasChanged方法來檢查模型的內部狀態,並確定從最初檢索模型時它的屬性發生了怎樣的變化。 該isDirty方法確定自檢索模型以來模型的任何屬性是否已更改。您可以將特定屬性名稱傳遞給該isDirty方法以確定特定屬性是否臟。在isClean將確定是否一個屬性保持不變,因為該模型被檢索。此方法還接受一個可選的屬性參數: ## groupBy groupBy可以簽套 用. ![](https://i.imgur.com/Jnbk9dN.png) ## 緩存查詢 ![](https://i.imgur.com/Trf5LxY.png) 使用上面的示例,假設我們緩存了用戶的查詢。現在假設已經創建、更新或刪除了一個新 用戶。緩存的查詢結果將不再有效且是最新的。為了解決這個問題,我們可以使用 Laravel 模型觀察器從緩存中刪除這個項目。 ## newInstance 除了基本你能用來當空的之外 這不會存到database 因為exists是false 你看create 其實就是fill 加上save 他會把exists改成true update 也會改成true 你可以想成現在是new然後還沒save但她可以用 **exists属性** save时会根据这个属性判断是insert还是update 都是要經過save 因為你可以看save的邏輯 Eloquent Model 获取模型时 (在 newFromBuilder 方法里) 会把 Model 实例的 exists 属性设置为 true ### save() 邏輯 https://learnku.com/articles/13106/laravel-source-code-reading-guide-model-additions-and-deletions-to-check-the-underlying-code-implementation ``` abstract class Model implements ... { public function save(array $options = []) { $query = $this->newQueryWithoutScopes(); if ($this->fireModelEvent('saving') === false) { return false; } //查询出来的Model实例的exists属性都是true if ($this->exists) { $saved = $this->isDirty() ? $this->performUpdate($query) : true; } else { $saved = $this->performInsert($query); if (! $this->getConnectionName() && $connection = $query->getConnection()) { $this->setConnection($connection->getName()); } } if ($saved) { $this->finishSave($options); } return $saved; } //判断对字段是否有更改 public function isDirty($attributes = null) { return $this->hasChanges( $this->getDirty(), is_array($attributes) ? $attributes : func_get_args() ); } //数据表字段会保存在$attributes和$original两个属性里,update前通过比对两个数组里各字段的值找出被更改的字段 public function getDirty() { $dirty = []; foreach ($this->getAttributes() as $key => $value) { if (! $this->originalIsEquivalent($key, $value)) { $dirty[$key] = $value; } } return $dirty; } protected function performUpdate(Builder $query) { if ($this->fireModelEvent('updating') === false) { return false; } if ($this->usesTimestamps()) { $this->updateTimestamps(); } $dirty = $this->getDirty(); if (count($dirty) > 0) { $this->setKeysForSaveQuery($query)->update($dirty); $this->fireModelEvent('updated', false); $this->syncChanges(); } return true; } //为查询设置where primary key = xxx protected function setKeysForSaveQuery(Builder $query) { $query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery()); return $query; } } ``` 在 save 里会根据 Model 实例的 exists 属性来判断是执行 update 还是 insert, 这里我们用的这个例子是 update,在 update 时程序通过比对 $attributes 和 $original 两个 array 属性里各字段的字段值找被更改的字段(获取 Model 对象时会把数据表字段会保存在 $attributes 和 $original 两个属性),如果没有被更改的字段那么 update 到这里就结束了,有更改那么就继续去执行 performUpdate 方法,performUpdate 方法会执行 Eloquent Builder 的 update 方法, 而 Eloquent Builder 依赖的还是数据库连接的 Query Builder 实例去最后执行的数据库 update。 ## 講解update 你有沒有想過這段代碼實際上返回了什麼? `$result = $products->whereNull('category_id')->update(['category_id' => 2]);` 我的意思是,更新是在數據庫中執行的,但那會$result包含什麼? 答案是受影響的行。所以如果你需要檢查有多少行受到影響,你不需要調用其他任何東西——update()方法會為你返回這個數字 **update 函数** save 函数仅仅支持手动的属性赋值,无法批量赋值。laravel 的 Eloquent Model 还有一个函数: update 支持批量属性赋值。有意思的是,Eloquent Builder 也有函数 update,那个是上一小节提到的 performUpdate 所调用的函数。 两个 update 功能一致,只是 Model 的 update 函数比较适用于更新从数据库取回的数据库对象: $flight = App\Flight::find(1); $flight->update(['name' => 'New Flight Name','desc' => 'test']); 而 Builder 的 update 适用于多查询条件下的更新: App\Flight::where('active', 1) ->where('destination', 'San Diego') ->update(['delayed' => 1]); 无论哪一种,都会自动更新 updated_at 字段。 Model 的 update 函数借助 fill 函数与 save 函数: ``` public function update(array $attributes = [], array $options = []) { if (! $this->exists) { return false; } return $this->fill($attributes)->save($options); } ``` 這邊是我自己想的 所以你看這邊有兩個參數 一般我們model都是 find之後->update 對吧 因為fill那邊會自動去找 可看fill解析 https://segmentfault.com/a/1190000012293039 ## 時間相關 .whereDate() 方法 `1 $q->where('created_at', '>=', date('Y-m-d').' 00:00:00'));` 以前查数据时,直接用where条件来比值判断,但是格式就会有严格的要求,如果上面的代码 第三个参数 是 date('Y-m-d') 而不加 后面的00:00:00 这样在数据库里面就会找不到 而判断是否相等 也都是对格式严格的要求 `$q->whereDate('created_at', '=', date('Y-m-d'));` 现在用whereDate,laravel自带的方法,就会自动帮你进行日期格式处理,保证date与搜索的格式是匹配的。 ``` $q->whereDay('created_at', '=', date('d')); $q->whereMonth('created_at', '=', date('m')); $q->whereYear('created_at', '=', date('Y')); ``` 如果要查date欄位的2021-8這樣 一定要用php轉成Carbon whereDate 跟 where都只吃Y-m-d ## 查詢聯合 查詢構建器還提供了一種將兩個或多個查詢“聯合”在一起的便捷方法。例如,您可以創建一個初始查詢並使用該union方法將其與更多查詢聯合: ``` use Illuminate\Support\Facades\DB; $first = DB::table('users') ->whereNull('first_name'); $users = DB::table('users') ->whereNull('last_name') ->union($first) ->get(); ``` 除了union方法之外,查詢構建器還提供了一種unionAll方法。使用該unionAll方法組合的查詢不會刪除其重複結果。該unionAll方法具有與方法相同的方法簽名union。 ## orderBy vs sortBy orderBy 是query `$clients = Client::orderBy('full_name')->get(); // doesn't work` sortBy 是collection `$clients = Client::get()->sortBy('full_name'); // works!` ## when ``` $sortBy = null; $users = DB::table('users') ->when($sortBy, function ($query, $sortBy) { return $query->orderBy($sortBy); }, function ($query) { return $query->orderBy('name'); }) ->get(); ``` Answer: 當 when 的第一個 parameter 為 true 時, 方執行第二個 parameter closure, 若第一個 parameter 為 false, 則執行第二個 closure ## newQuery vs newModelQuery 這邊是自己感覺的 ``` /** * Get a new query builder for the model's table. * * @return \Illuminate\Database\Eloquent\Builder */ public function newQuery() { return $this->registerGlobalScopes($this->newQueryWithoutScopes()); } /** * Get a new query builder that doesn't have any global scopes or eager loading. * * @return \Illuminate\Database\Eloquent\Builder|static */ public function newModelQuery() { return $this->newEloquentBuilder( $this->newBaseQueryBuilder() )->setModel($this); } ``` newQuery之前都會複寫的原因是要保證with之類的都是要用這個(全域的) newModelQuery這個是save會用到 他要保證乾淨的? ## where的closure https://stackoverflow.com/questions/15653216/combining-and-or-eloquent-query-in-laravel ![](https://i.imgur.com/X5VFY3a.png) ``` $externalValue = "User"; $users = User::where(function ($query) use($externalValue) { return $query->where('type','Admin')->orWhere('type',$externalValue); })->where("role",1)->get(); ``` ## has() and doesnthave() https://blog.johnsonlu.org/eloquent-has-and-doesnthave/ ![](https://i.imgur.com/vM0lL9l.png) ## orHas特別用法 平常是 has 沒的話就orHas這樣 ![](https://i.imgur.com/Pje1w4T.png) 這等於查詢出來的 有關連在has 但好像可以loadMissing就好 ## withCount 跟 withExists count會抓全部 exists找到就停下了 比較好 https://www.youtube.com/watch?v=mza7uzxnuyk ## firstOr first找不到要if_about去判斷 但用這個firstOr可以用必包 更快 https://www.youtube.com/watch?v=giJTIpsmtTc ## 子查詢高階簡化 a::with('b')->get() 這樣雖然 b那邊有減少n+1 但a這邊他會有查詢很多次model的關係 在資料庫查詢 是比php查詢快的 https://www.youtube.com/watch?v=oWbO6PhGy8o ex $user->lastLogin->ip 變成 $user->last_logged_in_at 可看上面截圖對照 ## groupBy 加上 having ``` <?php $users = DB::table('users') ->groupBy('first_name', 'status') ->having('account_id', '>', 100) ->get(); ``` Answer: 先依據給予的欄位 groupBy record, 在使用 having 篩選出需要的資料, having 在此相當於 where 的作用 記得因為groupBy是有多新的欄位名稱 所以才能用having 就像你用_count可以用having一樣 ## orderBy 重新設置 ``` <?php $query = DB::table('users')->orderBy('name'); $usersOrderedByEmail = $query->reorder('email', 'desc')->get(); ``` **Answer:** 移除現有的 orderBy constraint, 套用新的排序規則 ## 時間搜尋 另外,Eloquent 中有一些預定義的方法,與日期/時間相關: ``` User::whereDate('created_at', date('Y-m-d')); User::whereDay('created_at', date('d')); User::whereMonth('created_at', date('m')); User::whereYear('created_at', date('Y')); ``` ## Eloquent::when() – 不再使用 if-else https://laravel-news.com/eloquent-tips-tricks 我們中的許多人使用“if-else”編寫條件查詢,如下所示: ``` if (request('filter_by') == 'likes') { $query->where('likes', '>', request('likes_amount', 0)); } if (request('filter_by') == 'date') { $query->orderBy('created_at', request('ordering_rule', 'desc')); } ``` 但是有一個更好的方法——使用when(): ``` $query = Author::query(); $query->when(request('filter_by') == 'likes', function ($q) { return $q->where('likes', '>', request('likes_amount', 0)); }); $query->when(request('filter_by') == 'date', function ($q) { return $q->orderBy('created_at', request('ordering_rule', 'desc')); }); ``` 它可能不會感覺更短或更優雅,但最強大的是傳遞參數: **重點** ``` $query = User::query(); $query->when(request('role', false), function ($q, $role) { return $q->where('role_id', $role); }); $authors = $query->get(); ``` when方法僅在第一個參數為 時執行給定的閉包true。如果第一個參數是false,則不會執行閉包 ## query的運作流程 跟scope(包含創建一個新的class在裡面使用) https://www.youtube.com/watch?v=PG7aAgvHiMU 你可以看到這query都是Elo類型的 你可以重newQuery追 ![](https://i.imgur.com/gFfBtkS.png) 直接使用定義的 跟先query一樣意思 因為都會用到上面截圖那個 創建一個query ## 更新带关联的 Model (push) 在更新关联的时候,使用 push 方法可以更新所有 Model ``` class User extends Model { public function phone() { return $this->hasOne('App\Phone'); } } $user = User::first(); $user->name = "Peter"; $user->phone->number = '1234567890'; $user->save(); // 只更新 User Model $user->push(); // 更新 User 和 Phone Model ``` ## make make 方法 make 方法用于建立子模型对象,但是并不进行保存操作: ``` public function make(array $attributes = []) { return tap($this->related->newInstance($attributes), function ($instance) { $this->setForeignAttributesForCreate($instance); }); } ``` ## query取抓取要的 減少效能 不知道這個select 跟 get差別 https://www.youtube.com/watch?v=i7bCDtrMYp0 ## 查询Model更改的属性 $user = User::first(); $user->name; // John $user->name = 'Peter'; $user->save(); dd($user->getChanges()); // 输出: ``` [ 'name' => 'John', 'updated_at' => '...' ] ``` ## 查询修改前的Model信息 ``` $user = App\User::first(); $user->name; //John $user->name = "Peter"; //Peter $user->getOriginal('name'); //John $user->getOriginal(); //Original $user record ``` ## 要少的欄位 就不要全部get select 抓取要的欄位就好 減少內存 get跟 pluck是get 實例的(pluck比較偏向 重新組key value 不然get就能選屬性了) select偏向query SQL那種 https://www.youtube.com/watch?v=i7bCDtrMYp0 至於用get過濾屬性還是select https://stackoverflow.com/questions/27634790/difference-between-select-and-get-in-laravel-eloquent select()僅用於定義您想要的列。get()用於實際獲取結果(> 執行查詢),它還允許您指定列。 `DB::table('foo')->select(array('bar'));` 會不會執行任何東西。你仍然需要get()為 `DB::table('foo')->select(array('bar'))->get();` 現在您會收到一個只有bar列的結果。 同樣可以這樣做: `DB::table('foo')->get(array('bar'));` 所以單獨的語法 get()更快(意味著更短),而性能方面你不會注意到任何區別。 結論 get>select 然後要get的屬性要重組key value就用pluck ## get vs pluck 如果您嘗試從表中檢索單個列,那麼pluck方法就很好。 如果您使用get()方法,它將檢索有關子模型的所有信息,這可能會導致查詢和獲取結果的過程變慢 這兩個都會get 獲取實例 但pluck的key value可以自己組 ## 查詢with沒有的 用閉包去查 然後用doesHave 只是要閉包名稱會變成where 小提示 這是and的 or要用 doesHave 閉包裡面第二參數用'or' https://stackoverflow.com/questions/28065566/laravel-wheredoesnthave-multiple-or-conditions ![](https://i.imgur.com/YEsdpDx.png) ## 嵌套with 用點會只有父跟孫 但是父 子 孫 都要有 ![](https://i.imgur.com/7vb14B1.jpg) ## 避免使用前置匹配符號(like 關鍵字) 通常如果我們想使用 like 去找出資料,我們會包成這樣的 like 語法: **select * from table_name where column like %keyword%** 但是這樣會導致『全資料表掃描』的資料庫行為,也就是沒有用到該欄位的索引(如果該欄位設有索引),因此是比較慢的一種動作。 如果我們知道前置詞為何,可以只使用這樣的語法 **select * from table_name where column like keyword%** 關於全資料表掃描,也就是 Full Table Scans 問題,可以參考 MySQL 官方的說明 MySQL — Avoiding Full Table Scans ## chunkById vs chunk chunkById 預設會使用 orderBy id, 並且會記住上一個 chunk 的 last id 效能上使用 chunkById 更好, 因為 chunk 是採用 offset … limit, 這樣當資料越來越多的時候, 會造成不必要的效能浪費, 而 chunkById 是採用 where id > $lastId, 因為效能上較佳 再者, 因為 chunkById 預設會 orderby id, 所以不會有 chunk 跟 chunk 之間因為順序錯亂而丟失 item 的情況 ## 觀念題 重點 記住只有find會直接找到 如果是builder系列 出來都會是collection 所以像where這種builder出來 就會是collection 還要再first一樣 ``` /** * 取得category下拉選單. */ public function getCategoriesOptions(): Collection { return $this->categoryRepository ->newQuery() ->get() ->pluck('name', 'id') ->prepend('請選擇分類', null); } ``` ## 8.57 whereHas 用 whereRelation取代(可以更簡短) https://www.youtube.com/watch?v=QxpoOegTVuc ## 兩個時間的天數 ![](https://i.imgur.com/b4TgsE3.jpg) https://www.youtube.com/watch?v=bJkEBTg1NGA ## get vs pluck pluck我覺得是給collection用 你只要專取限定的就`get(['id','name'])`就可以了 ## 鎖 rows 不給update `DB::table('users')->where('votes', '>', 100)->sharedLock()->get();` **Answer:** 鎖住指定的 rows, 在解除鎖定之前, 這些 rows 無法被 update ``` ?php DB::table('users')->where('votes', '>', 100)->lockForUpdate()->get(); ``` **Answer:** 鎖住指定的 rows, 在解除鎖定之前, 這些 rows 無法被 update, 也無法被 select ## count 不要collection再用 with::count比較好 ## pluck 可以自己組key value(下拉選單) /** * 取得分類選項格式. */ ``` public function getCategoriesOptions(): Collection { return $this->categoryRepository ->newQuery() ->get() ->pluck('name', 'id') ->prepend('請選擇分類', null); } ``` ## pluck vs selct **只選擇需要的欄位** 在撈取資料時,相信大家在方便的情形下,通常不會特別設定 select 哪些欄位,最常直接帶 select `*` 這樣的語句,但在資料庫層撈取的時候,這會帶給撈取資料很大的負擔,所以推薦用以下方式,只選擇需要的欄位: 轉化成的 SQL: `select id,title from posts where id = 1 limit 1` 至於效率優化有多明顯呢?舉個筆者公司一個專案的表,裡面裡面共有 323695 筆資料、27個欄位,透過本地連線直接全撈,抓到吻合資料的時間約 0.118 秒,但顯示資料卻花了 52.76 秒。 如果我只撈取關鍵要的 5 個欄位,則可以將顯示資料時間大幅降至 9.391 秒 **當只需要 1–2 個欄位,使用 pluck** 通常引用第二點的方式,我們可能會用下面程式撈取特定欄位顯示: 不過如果我們『真的』只要 title 和 slug 的資料,那這對 Laravel 來說可能是個不必要的負擔。 因為假設按上述程式撈取了資料,Laravel 會執行以下工作 ``` 1. 資料庫執行 select title, slug from posts 語句 2. 替每一列找到的資料,建立一個 model 物件 3. 替這些 model 物件,建立一個 collection 物件 4. 回傳 collection ``` 這時,我們的 Laravel 記憶體中,就多了許多不必要的物件,因為我們只要值,不需要物件的提供的功能。 因此如果改寫成 pluck 則他的步驟是 ``` 1. 資料庫執行 select title, slug from posts 語句 2. 建立一個 array,裡面有這些 rows, slug 是 key、title 是 value 3. 回傳 array ( 結構為 [ slug => title, slug => title ] ) ``` array 的操作顯示,比 collection 和 model 輕量多了。 如果只有 1 個欄位: 則就會是標準意義上的 array,key 的部分是有序列整數。 ## 數量多用 chunkById 分組 ## having 跟 where差別 ![](https://i.imgur.com/VZYE75P.png) 像這個blop_post_count 不是資料庫有的 就用having ## 確切的數據庫錯誤 如果您想捕獲 Eloquent Query 異常,請使用特定的QueryException而不是默認的 Exception 類,您將能夠獲得錯誤的確切 SQL 代碼。 ``` try { // Some Eloquent/SQL statement } catch (\Illuminate\Database\QueryException $e) { if ($e->getCode() === '23000') { // integrity constraint violation return back()->withError('Invalid data'); } } ``` ## 判斷查詢有沒有 不一定要用first在判斷數量 用exists() ![](https://i.imgur.com/k3XcJTg.jpg) ## 判斷有沒有關聯 `$category->fAQs()->count() > 0)` 不管是不是多對多就用count就好 ## all 就等於 newQuey 只是all多一個能傳的欄位 可看文黨 ``` /** * Get all of the models from the database. * * @param array|mixed $columns * @return \Illuminate\Database\Eloquent\Collection|static[] */ public static function all($columns = ['*']) { return static::query()->get( is_array($columns) ? $columns : func_get_args() ); } ``` ## when query的if eles when 當你是學生就 .. 當你是老師就 ... ![](https://i.imgur.com/bsiOod8.jpg) ## orderBy 很重要一點是會有第一第二排序 ![](https://i.imgur.com/ibv90FI.jpg) 這種 例如第一排序用id,這樣相同的會變成group,第二排序再用隨便一個,就都不會受影響 ## withCount withSum https://www.icat.tw/laravel-8-12-%E7%9A%84%E6%96%B0%E5%8A%9F%E8%83%BD%EF%BC%9A%E5%8A%A0%E7%B8%BD%E3%80%81%E6%9C%80%E5%A4%A7%E5%80%BC%E3%80%81%E5%B9%B3%E5%9D%87%E5%80%BCon-relationships/ ###### tags: `Laravel`