Laravel Eloquent ORM - fantasy0107/notes GitHub Wiki

用物件的方式操作 DB record

Model

概念

  1. 設定下面 properties
  2. relation
  3. append 當需要再 append
  4. query 部分丟到 Repository

產生檔案

php artisan make:model User

和 migration + controller + controller resourceful 一起產生

php artisan make:model Company -mcr

-m migration
-c controller 
-r resourceful

property

class User extends Model
{
    protected $table = 'my_flights'; // 使用的 table
    protected $primaryKey = 'id'; // 預設就是 id
    public $incrementing = true; // 預設主鍵會自動增加
    protected $connection = 'connection-name'; //沒有就用預設的
    public $timestamps = true; // 預設會有 created_at 和 updated_at
    protected  $casts = ['is_admin' => 'boolean']; //轉換資料型態
    protected $appends = ['a']; // 每次取時要附加的 accessors 
    
    protected $hidden = ['password']; //要隱藏的欄位
    protected $visible = ['id']; //要顯示的欄位和 hidden 擇一
    
    protected $fillable = ['name']; //可以存值
    protected $guarded = ['id']; //不能存值和 fillable 擇一
    protected $with = ['user']; // 要 eager load 的
    
}

Database Connection

protected $connection = 'connection-name';

跨 DB

protected $table = 'otherDBname.my_flights'; // 使用的 table

切換成用 write connection

Models::onWriteConnection();

append 的時候

要注意有些是需要跑 DB query的不要自動 append 上去

參考資料

Eloquent: Getting Started
[Laravel API] (https://laravel.com/api/5.5/Illuminate/Database/Eloquent/Model.html)

API Resources

轉換 Eloquetn 回傳欄位
方便同一個 model 或 collection
需要再不同地方顯示不同column

產生檔案

命名: modelName + Resource

php artisan make:resource UserResource

檔案位置

App\Http\Resources

使用

model

return new UserResource(User::find(1));

collection

return UserResource::collection(User::all());

結構

$this->column 是取得對應的eloquent欄位

public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

參考資料

Laravel API Resources — Under the hood

Frequently Asked Questions About Laravel based APIs

Defining Models

ActiveRecord implementation
database table => Model
config/database.php

產生model + migration
php artisan make:model User --migration

Primary Keys

  • $primaryKey
  • $incrementing
  • $keyType

Timestamps

class Flight extends Model
{
    public $timestamps = false;
}
  • 設定timestamp格式
class Flight extends Model
{
    protected $dateFormat = 'U';
}
  • 設定創造和更新時間
<?php

class Flight extends Model
{
    const CREATED_AT = 'creation_date';
    const UPDATED_AT = 'last_update';
}

Database Connection

class Flight extends Model
{
    protected $connection = 'connection-name';
}

Retrieving Models

Query Builder

collections

  • all + get
  • collection helper
  • loop like an array

chunking Results

  • 當需要處理大量的資料
Flight::chunk(200, function ($flights) {
    foreach ($flights as $flight) {
        //do something
    }
});
  • 使用cursor - 可以減少記憶體的使用當需要處理大量資料
foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
    // do something
}

Mutators

  1. Accessor - Model 自定義屬性
  2. Mutator - Model 儲存值時轉換輸入進來的值
  3. Date Mutators - Model 轉換這些欄位為 date
  4. Attribute Casting - Model property 轉換

Accessor

getPropertyNameAttribute
studly cased name

class User extends Model
{
    public function getFirstNameAttribute($value)
    {
        return ucfirst($value);
    }
}

Query Scopes

Global Scopes

增加所有對model query的限制

怎麼寫Global Scopes

//範例
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class AgeScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('age', '>', 200);
    }
}

使用global scopes

<?php
namespace App;

use App\Scopes\AgeScope;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected static function boot()
    {
        parent::boot();
        static::addGlobalScope(new AgeScope);
    }
}

User::all() => select * from `users` where `age` > 200

匿名 Global Scopes

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class User extends Model
{
    protected static function boot()
    {
        parent::boot();
        static::addGlobalScope('age', function (Builder $builder) {
            $builder->where('age', '>', 200);
        });
    }
}

移除 Global Scopes

// 移除全部 global scopes...
User::withoutGlobalScopes()->get();

// 移除部分 global scopes...
User::withoutGlobalScopes([
    FirstScope::class, SecondScope::class
])->get();

Local Scopes

定義可以重複使用的限制

定義

<?php

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

使用

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

動態Scopes

有的時候需要傳入變數
<?php
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();

Deleting Models

  • 刪除model
$flight = App\Flight::find(1);

$flight->delete();
  • 用key刪除存在的model
App\Flight::destroy(1); //單一
App\Flight::destroy([1, 2, 3]); //複數
App\Flight::destroy(1, 2, 3); //複數
  • 藉由query刪除model
$deletedRows = App\Flight::where('active', 0)->delete();

Soft Deleting(軟刪除)

  • 當不是真的想要從資料庫中刪除這筆資料
  • deleted_at => 非null代表已經被軟刪除
  • 要使用軟刪除 => Illuminate\Database\Eloquent\SoftDeletes + deleted_at必須在資料庫中
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;//軟刪除

class Flight extends Model
{
    use SoftDeletes;

    /**
     * The attributes that should be mutated to dates.
     *
     * @var array
     */
    protected $dates = ['deleted_at'];
}
  • Schema builder helper
Schema::table('flights', function ($table) {
    $table->softDeletes();
});
  • 判斷model是否被軟刪除
if ($flight->trashed()) {
    //
}

Querying Soft Deleted Models

  • 包括Soft Deleted Models
$flights = App\Flight::withTrashed()
                     ->where('account_id', 1)
                     ->get();
  • withTrashed 可以用在relationship Query
$flight->history()->withTrashed()->get();
  • 只擷取軟刪除的model
$flights = App\Flight::onlyTrashed()
                ->where('airline_id', 1)
                ->get();
  • 回復軟刪除的model
$flight->restore(); //單一

App\Flight::withTrashed()
        ->where('airline_id', 1)
        ->restore();// 複數
        
$flight->history()->restore(); //關聯
  • 永久的刪除model
// Force deleting a single model instance...
$flight->forceDelete();//單一

// Force deleting all related models...
$flight->history()->forceDelete(); //關聯

Mutator

class User extends Model
{
    public function setFirstNameAttribute($value)
    {
        $this->attributes['first_name'] = strtolower($value);
    }
}

Date Mutators

取得 $dates 內設定的 property 回傳都會是 Carbon instances

當儲存以下的值時會自動轉成正確的格式儲存進DB

  1. UNIX timestamp
  2. date string (Y-m-d)
  3. date-time string
  4. DateTime / Carbon instance
class User extends Model
{
    protected $dates = [
        'created_at',
        'updated_at',
        'deleted_at'
    ];
}

Attribute Casting

支援的轉換格式

  1. integer
  2. real
  3. float
  4. double
  5. string
  6. boolean
  7. object
  8. array
  9. collection
  10. date
  11. datetime
  12. timestamp
class User extends Model
{
    protected $casts = [
        'is_admin' => 'boolean',
    ];
}

轉換 is_admin 為 boolean value, 如果原本是 DB 中是 0 或 1 就會轉換成相對應的 Boolean value

注意

轉換 DB 欄位中的 serialized JSON

當serialize json 被轉換成 array 那麼重新儲存值後更新會被轉換回 serialize json

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

$options = $user->options;

$options['key'] = 'value';

$user->options = $options;

$user->save();

概念

  1. 不要先將 Accessors append 要使用的時候再append

Events

Model lifecycle

  • retrieved - 當存在的model被取得
  • creating/created - 在資料庫中存在的model且save被呼叫
  • updating/updated -同 creating/created
  • saving
  • saved
  • deleting
  • deleted
  • restoring
  • restored
//範例
<?php

namespace App;

use App\Events\UserSaved;
use App\Events\UserDeleted;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The event map for the model.
     *
     * @var array
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}

Observers

當你需要在MODEL上監聽很多事件

產生Observer

//範例
<?php

namespace App\Observers;

use App\User;

class UserObserver
{
    /**
     * Listen to the User created event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function created(User $user)
    {
        //
    }

    /**
     * Listen to the User deleting event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function deleting(User $user)
    {
        //
    }
}

註冊Observer

//範例
<?php

namespace App\Providers;

use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        User::observe(UserObserver::class);
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Inserting & Updating Models

inserts

  1. new出model
  2. 給予model所需要的值
  3. save
$flight = new Flight;//new出flight model

$flight->name = $request->name;

$flight->save();//存入DB + created_at和updated_at會被更新

updates

  1. 取得需要的model
  2. 對欄位進行更新
  3. save
$flight = App\Flight::find(1);

$flight->name = 'New Flight Name';

$flight->save();

mass updates

  • 對大量的model進行更新
App\Flight::where('active', 1)
          ->where('destination', 'San Diego')
          ->update(['delayed' => 1]);//是個array columneName => value

mass assignment

  • fillable(可以儲存值) / guardered(不能儲存值)
class Flight extends Model
{
    //The attributes that are mass assignable.
    protected $fillable = ['name'];
}
  • 當有model instance可以用fill來填充陣列的屬性
$flight->fill(['name' => 'Flight 22']);

Guarding Attributes

  • 不能被儲存值
class Flight extends Model
{
    //The attributes that aren't mass assignable.
    protected $guarded = ['price'];
}
  • 所有屬性都不能被儲存值
protected $guarded = [];

other creation methods

firstOrCreate/ firstOrNew

  • 找出或創造model
  • 差別在firstOrNew需要save
// Retrieve flight by name, or create it if it doesn't exist...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);

// Retrieve flight by name, or create it with the name and delayed attributes...
$flight = App\Flight::firstOrCreate(
    ['name' => 'Flight 10'], ['delayed' => 1]
);

// Retrieve by name, or instantiate...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);

// Retrieve by name, or instantiate with the name and delayed attributes...
$flight = App\Flight::firstOrNew(
    ['name' => 'Flight 10'], ['delayed' => 1]
);

updateOrCreate

  • 更新或創造model
// If there's a flight from Oakland to San Diego, set the price to $99.
// If no matching model exists, create one.
// 不需要save() 就可以儲存進DB
$flight = App\Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99]
);

Serialization

Appending Values To JSON

\\Mode
...
public function getIsAdminAttribute()
    {
        return $this->attributes['admin'] == 'yes';
    }
...
\\Controller
return $user->append('is_admin')->toArray();

Relationships

問題 hasManyThrought 的參數

// 拿到 countries 的 posts
countries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title - string


public function posts()
    {
        return $this->hasManyThrough(
            'App\Post', // 目標的model
            'App\User', // 中介的model
            'country_id', // Foreign key on users table... // 中介model 參考的 foregin key
            'user_id', // Foreign key on posts table... // 目標model 參考的 foregin key
            'id', // Local key on countries table... // 主要model 的 primary key
            'id' // Local key on users table... // 中介model 的 primary key 
        );
    }