Laravel 學習筆記

# 前言

本篇為 Laravel 的學習筆記, 主要將看到的, 學到的技術轉換成 Q&A 的方式以加速學習




# Production 優化

# PHP

php.ini 文件

php.ini 中的 memory_limit 用於設定單個 PHP process 可以使用的系統內存最大值


# Questions and Answers

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $customers = Customer::inRandomOrder()->take(1)->get();
    $regions = Region::hasCustomer($customers->first())->get();

    return view('customers', [
    'customers' => $customers,
    'regions' => $regions,
    ]);
    }

    public function scopeHasCustomer($query, Customer $customer)
    {
    $query->whereRaw('ST_Contains(regions.geometry, ?)', [$customer->location]);
    }
  • Answer:

    <?php
    public function index()
    {
    // 取得 random user
    $customers = Customer::inRandomOrder()->take(1)->get();
    // 取得該 customer 所在的 region
    $regions = Region::hasCustomer($customers->first())->get();

    return view('customers', [
    'customers' => $customers,
    'regions' => $regions,
    ]);
    }

    public function scopeHasCustomer($query, Customer $customer)
    {
    // 使用 ST_Contains function, arg1 為 regions table 的 geometry column, arg2 為帶入的 customer location, 以取得該 customer 所在的 region
    $query->whereRaw('ST_Contains(regions.geometry, ?)', [$customer->location]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $regions = Region::all();
    $customers = Customer::query()
    ->inRegion(Region::where('name', 'The Prairies')->first())
    ->get();

    return view('customers', [
    'customers' => $customers,
    'regions' => $regions,
    ]);
    }

    public function scopeInRegion($query, Region $region)
    {
    $query->whereRaw('ST_Contains(?, customers.location)', [$region->geometry]);
    }
  • Answer:

    <?php
    public function index()
    {
    $regions = Region::all();
    // 目標在於 select 出, 位於 'The Prairies' 這個 region 的 user
    $customers = Customer::query()
    ->inRegion(Region::where('name', 'The Prairies')->first())
    ->get();

    return view('customers', [
    'customers' => $customers,
    'regions' => $regions,
    ]);
    }

    public function scopeInRegion($query, Region $region)
    {
    // 使用 ST_Contains, arg1 為 geometry format, arg2 為 user location, 所以會 select 出 location 位於此 geometry 的 users
    $query->whereRaw('ST_Contains(?, customers.location)', [$region->geometry]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    protected function seedRegions()
    {
    $this->getRegions()->each(fn ($region) => Region::create([
    'name' => $region['name'],
    'color' => $region['color'],
    'geometry' => (function () use ($region) {
    return DB::raw("ST_SRID(ST_GeomFromText('".$region['geometry']."'), 4326)");
    })(),
    ]));
    }
  • Answer:
    取得 Regions 後, 在 Region table 建立資料, geometry column, 使用 ST_SRID 來設定 spatial reference ID, 4326 表示 world, 使用 ST_GeomFrom Text function, 從 text 建立 geometry data, geometry 格式如下:

    'geometry' => 'MultiPolygon (((-136.21070809681475566 57.03101434136195991, -133.72877891703720366 54.61201637520210284, -133.47202762257742847 53.45604439614419334, -131.01862636440651499 51.6878513109907729, -126.28299137770447658 48.97546429328855311, -125.05629074861900563 48.48622934024960074, -123.37102414358200519 48.11059520996249717, -122.52069777133300477 48.99007009784259736, -113.96448759224449532 48.98897113447335272, -114.59407626641420563 50.4166998690063437, -119.86365984910806048 53.47639943303917676, -119.94099441499190561 59.97238022147455894, -140.92938615814153991 60.06202748779399059, -140.74999819452486349 59.61135341849296054, -138.11897472814712273 58.90840120048653006, -136.21070809681475566 57.03101434136195991)))',
以下的 Laravel example code 中, Multipolygon 的意思是?
  • Example:

    public function getRegions()
    {
    return collect([
    [
    'name' => 'British Columbia',
    'color' => '#F56565',
    'geometry' => 'MultiPolygon (((-136.21070809681475566 57.03101434136195991, -133.72877891703720366 54.61201637520210284, -133.47202762257742847 53.45604439614419334, -131.01862636440651499 51.6878513109907729, -126.28299137770447658 48.97546429328855311, -125.05629074861900563 48.48622934024960074, -123.37102414358200519 48.11059520996249717, -122.52069777133300477 48.99007009784259736, -113.96448759224449532 48.98897113447335272, -114.59407626641420563 50.4166998690063437, -119.86365984910806048 53.47639943303917676, -119.94099441499190561 59.97238022147455894, -140.92938615814153991 60.06202748779399059, -140.74999819452486349 59.61135341849296054, -138.11897472814712273 58.90840120048653006, -136.21070809681475566 57.03101434136195991)))',
    ],
    ]);
    }
  • Answer:
    用多個 point 來代表一個區域

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('regions', function (Blueprint $table) {
    $table->geometry('geometry');
    });
    }
  • Answer:
    geometry column type, 可以用多個 point 連起來代表一個區塊

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $myLocation = [-79.47, 43.14];

    $stores = Store::query()
    ->selectDistanceTo($myLocation)
    ->withinDistanceTo($myLocation, 10000) // 10km
    ->orderByDistanceTo($myLocation)
    ->paginate();

    return view('stores', ['stores' => $stores]);
    }

    public function scopeOrderByDistanceTo($query, array $coordinates, string $direction = 'asc')
    {
    $direction = strtolower($direction) === 'asc' ? 'asc' : 'desc';

    $query->orderByRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) '.$direction, $coordinates);
    }
  • Answer:

    <?php
    public function scopeOrderByDistanceTo($query, array $coordinates, string $direction = 'asc')
    {
    // 由於 $direction 是從外部帶入, 若要直接使用於 orderByRaw, 需要消毒
    $direction = strtolower($direction) === 'asc' ? 'asc' : 'desc';

    // ST_Distance function 取得距離, 第一個 location 為資料庫中的 location column, 第二個為 request user
    // 的 location, 由於 ST_Distance 只接受 valid geographic object, 所以要先轉成 SRID
    // 空一格之後, 再接 $direction
    $query->orderByRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) '.$direction, $coordinates);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('stores', function (Blueprint $table) {
    $table->point('location', 4326);
    });
    }
  • Answer:
    point 為 geographic 的一種形式, 需使用類似 $user->location = \Illuminate\Support\Facades\DB::raw('ST_SRID(Point('.$l.', '.$a.'), 4326)'); 這種方式儲存, $l 為longitude, $a 為 Latitude

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $myLocation = [-79.47, 43.14];

    $stores = Store::query()
    ->selectDistanceTo($myLocation)
    ->withinDistanceTo($myLocation, 10000) // 10km
    ->paginate();

    return view('stores', ['stores' => $stores]);
    }


    public function scopeWithinDistanceTo($query, array $coordinates, int $distance)
    {
    $query->whereRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) <= ?', [...$coordinates, $distance]);
    }
  • Answer:

    <?php
    public function index()
    {
    $myLocation = [-79.47, 43.14];

    $stores = Store::query()
    // select 出 distance column
    ->selectDistanceTo($myLocation)
    // 取得距離在 10km 裡的 record
    // ST_DISTANCE function 得到的結果為 meter
    // 而 10000 meter = 10km
    ->withinDistanceTo($myLocation, 10000) // 10km
    ->paginate();

    return view('stores', ['stores' => $stores]);
    }


    public function scopeWithinDistanceTo($query, array $coordinates, int $distance)
    {
    // 使用 ST_Distance function
    // arg1 為 store 的 location, 可使用 point type column
    // 在儲存時就存成 point 格式, 這樣會增進 select 效能
    // Point(?, ?) 為當前 User 的座標
    // 最後取得 distance <= 10km 的 record
    $query->whereRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) <= ?', [...$coordinates, $distance]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $myLocation = [-79.47, 43.14];

    $stores = Store::query()
    ->selectDistanceTo($myLocation)
    ->paginate();

    return view('stores', ['stores' => $stores]);
    }

    public function scopeSelectDistanceTo($query, array $coordinates)
    {
    if (is_null($query->getQuery()->columns)) {
    $query->select('*');
    }

    $query->selectRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) as distance', $coordinates);
    }
  • Answer:

    <?php
    public function index()
    {
    $myLocation = [-79.47, 43.14];

    // 取得當前 user 的座標與各個 store 之間的距離
    $stores = Store::query()
    ->selectDistanceTo($myLocation)
    ->paginate();

    return view('stores', ['stores' => $stores]);
    }

    public function scopeSelectDistanceTo($query, array $coordinates)
    {
    // 如果當前 $query 沒有取得任何 column 的話, 那就 select 所有 column
    // 因為如果沒這麼做的話, 下面的 query 只會取得 selectRaw 的那一個 column
    if (is_null($query->getQuery()->columns)) {
    $query->select('*');
    }

    // 使用 MySQL 的 ST_Distance 來取得兩個點的距離
    // ST_Distance function 要求的 args 必須要是 valid geographic object
    // 所以使用 ST_SRID function 來取得
    // ST_SRID args 為兩個座標, 以及使用的 spatial reference ID, 4326 代表World
    $query->selectRaw('ST_Distance(
    location,
    ST_SRID(Point(?, ?), 4326)
    ) as distance', $coordinates);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    DB::statement('CREATE FULLTEXT INDEX posts_fulltext_index ON posts(title, body) WITH PARSER ngram');
    }
  • Answer:
    加入 fulltext index 到 posts table 的 title, body column, 使用 ngram parser 取代預設 parser

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $posts = Post::query()
    ->with('author')
    ->when(request('search'), function ($query, $search) {
    $query->whereRaw('match(title, body) against(? in boolean mode)', [$search])
    ->selectRaw('*, match(title, body) against(? in boolean mode) as score', [$search]);
    }, function ($query) {
    $query->latest('published_at');
    })
    ->paginate();

    return view('posts', ['posts' => $posts]);
    }
  • Answer:

    <?php
    public function index()
    {
    $posts = Post::query()
    ->with('author')
    // 當 $request->search === true
    ->when(request('search'), function ($query, $search) {
    // 使用 match method, 須事先先加 full-text index, boolean mode 效能較佳
    $query->whereRaw('match(title, body) against(? in boolean mode)', [$search])
    // 增加 score column 代表命中程度
    ->selectRaw('*, match(title, body) against(? in boolean mode) as score', [$search]);
    // 當 $request->search === false, 照 published_at 排序, 因為 match score 排序跟 published_at 排序不同
    }, function ($query) {
    $query->latest('published_at');
    })
    ->paginate();

    return view('posts', ['posts' => $posts]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $devices = Device::query()
    ->orderByRaw('naturalsort(name)')
    ->paginate();

    return view('devices', ['devices' => $devices]);
    }
  • Answer:
    使用 natual sort, 但 MySQL 不支援, 所以必須先在 MySQL 中增加 natualsort function

MySQL 不支援 natural sort, 該怎麼做?

自己增加一個 MySQL function, 如下:

  • Example:
    <?php
    public function up()
    {
    if (config('database.default') === 'mysql') {
    // https://www.drupal.org/project/natsort
    DB::unprepared("
    drop function if exists naturalsort;
    create function naturalsort(s varchar(255)) returns varchar(255)
    no sql
    deterministic
    begin
    declare orig varchar(255) default s;
    declare ret varchar(255) default '';
    if s is null then
    return null;
    elseif not s regexp '[0-9]' then
    set ret = s;
    else
    set s = replace(replace(replace(replace(replace(s, '0', '#'), '1', '#'), '2', '#'), '3', '#'), '4', '#');
    set s = replace(replace(replace(replace(replace(s, '5', '#'), '6', '#'), '7', '#'), '8', '#'), '9', '#');
    set s = replace(s, '.#', '##');
    set s = replace(s, '#,#', '###');
    begin
    declare numpos int;
    declare numlen int;
    declare numstr varchar(255);
    lp1: loop
    set numpos = locate('#', s);
    if numpos = 0 then
    set ret = concat(ret, s);
    leave lp1;
    end if;
    set ret = concat(ret, substring(s, 1, numpos - 1));
    set s = substring(s, numpos);
    set orig = substring(orig, numpos);
    set numlen = char_length(s) - char_length(trim(leading '#' from s));
    set numstr = cast(replace(substring(orig,1,numlen), ',', '') as decimal(13,3));
    set numstr = lpad(numstr, 15, '0');
    set ret = concat(ret, '[', numstr, ']');
    set s = substring(s, numlen+1);
    set orig = substring(orig, numlen+1);
    end loop;
    end;
    end if;
    set ret = replace(replace(replace(replace(replace(replace(replace(ret, ' ', ''), ',', ''), ':', ''), '.', ''), ';', ''), '(', ''), ')', '');
    return ret;
    end;
    ");
    }

    if (config('database.default') === 'sqlite') {
    throw new \Exception('This lesson does not support SQLite.');
    }

    if (config('database.default') === 'pgsql') {
    // http://www.rhodiumtoad.org.uk/junk/naturalsort.sql
    DB::unprepared('
    create or replace function naturalsort(text)
    returns bytea language sql immutable strict as
    $f$ select string_agg(convert_to(coalesce(r[2],length(length(r[1])::text) || length(r[1])::text || r[1]),\'SQL_ASCII\'),\'\x00\')
    from regexp_matches($1, \'0*([0-9]+)|([^0-9]+)\', \'g\') r; $f$;
    ');
    }
    }

    public function down()
    {
    if (config('database.default') === 'mysql') {
    DB::unprepared('drop function if exists naturalsort');
    }

    if (config('database.default') === 'sqlite') {
    throw new \Exception('This lesson does not support SQLite.');
    }

    if (config('database.default') === 'pgsql') {
    DB::unprepared('drop function if exists naturalsort');
    }
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $devices = ['iPhone 3', 'iPhone 11'];
    sort($devices, SORT_NATUAL);
    return $devices;
    }
  • Answer:
    使用 PHP 的 natual sort 參數, 正常 sort 是 iPhone 11, iPhone 3, natual sort 是 iPhone 3, iPhone 11

什麼是 natual sorting?

跟 Alphabetical Sort 幾乎相同, 唯一的不同在於, natual sorting 會將多位數的數字當成一個單一字符
換句話說, ['3', '11', '2'] Alphabetical 排序會是 11, 2, 3, 而 natual sort 會是 2, 3, 11

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function scopeOrderByUpcomingBirthdays()
    {
    $query->orderByRaw('
    case
    when (birth_date + interval (year(?) - year(birth_date)) year) >= ?
    then (birth_date + interval (year(?) - year(birth_date)) year)
    else (birth_date + interval (year(?) - year(birth_date)) + 1 year)
    end
    ', [
    array_fill(0, 4, Carbon::now()->startOfWeek()->toDateString()),
    ]);
    }
  • Answer:

    <?php
    public function scopeOrderByUpcomingBirthdays()
    {
    // 概念為, 將 'birth_date' 加上 '今年到你生日那年總共間隔多少年' 會等於你的 birth_date, 但
    // 年份換成是今年, 如果這個值大於今天的年月日的話, 那代表你的生日還沒過, 反之, 如果這個值小於的話
    // 那代表今年你的生日已經過了, 所以會排到明年去。
    // 這樣一來便可以 order by upcoming birthday
    $query->orderByRaw('
    case
    when (birth_date + interval (year(?) - year(birth_date)) year) >= ?
    then (birth_date + interval (year(?) - year(birth_date)) year)
    else (birth_date + interval (year(?) - year(birth_date)) + 1 year)
    end
    ', [
    // 從 index 0 開始 fill, fill 4 次, fill 這個禮拜的第一天, 以 dateString 的格式
    array_fill(0, 4, Carbon::now()->startOfWeek()->toDateString()),
    ]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function test()
    {
    Carbon::setTestNow(Carbon::parse('January 1, 2020'))
    }
  • Answer:
    使用 setTestNow() 可以設定 Carbon 的當前時間

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->whereBirthdayThisWeek()
    ->orderByBirthday()
    // ->orderByUpcomingBirthdays()
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeOrderByBirthday($query)
    {
    $query->orderByRaw('date_format(birth_date, "%m-%d")');
    }
  • Answer:

    <?php
    public function index()
    {
    $users = User::query()
    ->whereBirthdayThisWeek()
    ->orderByBirthday()
    // ->orderByUpcomingBirthdays()
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeOrderByBirthday($query)
    {
    // order by raw 'birth_date' 欄位, 並以 "%m-%d" 格式排序
    $query->orderByRaw('date_format(birth_date, "%m-%d")');
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    $table->rawIndex("(date_format(birth_date, '%m-%d')), name", 'users_birthday_name_index');
    });
    }
  • Answer:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    // 增加 compound index, 欄位為使用 date_format function
    // reformat 過的 birth_date column, 以及 name column
    $table->rawIndex("(date_format(birth_date, '%m-%d')), name", 'users_birthday_name_index');
    });
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->whereBirthdayThisWeek()
    ->orderByBirthday()
    // ->orderByUpcomingBirthdays()
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeWhereBirthdayThisWeek($query)
    {
    $dates = Carbon::now()->startOfWeek()
    ->daysUntil(Carbon::now()->endOfWeek())
    ->map(fn ($date) => $date->format('m-d'));

    $query->whereRaw('date_format(birth_date, "%m-%d") in (?,?,?,?,?,?,?)', iterator_to_array($dates));
    }
  • Answer:

    <?php
    public function index()
    {
    $users = User::query()
    // 取得生日在本週的 User model
    ->whereBirthdayThisWeek()
    ->orderByBirthday()
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeWhereBirthdayThisWeek($query)
    {
    // 取得 this week 的每一個 date 的日期, 並轉成 'm-d' 格式
    $dates = Carbon::now()->startOfWeek()
    ->daysUntil(Carbon::now()->endOfWeek())
    ->map(fn ($date) => $date->format('m-d'));

    // 取得 birth_date 為這個禮拜的 model, 之所以使用 in 而不是 between, 那是
    // 因為當遇到 12-25 到 1-2 的情況時, 因為前者比後者大, 會出現 query 沒有結果的
    // 問題, 而之所以使用 iterator_to_array, 那是因為 Carbon 會 return 一個
    // generator object
    $query->whereRaw('date_format(birth_date, "%m-%d") in (?,?,?,?,?,?,?)', iterator_to_array($dates));
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('features', function (Blueprint $table) {
    $table->rawIndex("(
    case
    when status = 'Requested' then 1
    when status = 'Approved' then 2
    when status = 'Completed' then 3
    end
    )", 'features_status_ranking_index');
    });
    }
  • Answer:

    <?php
    public function up()
    {
    Schema::create('features', function (Blueprint $table) {
    // 當使用 orderRaw("(
    // case
    // when status = 'Requested' then 1
    // when status = 'Approved' then 2
    // when status = 'Completed' then 3
    // end
    // )")
    // 時, 可加上 index
    $table->rawIndex("(
    case
    when status = 'Requested' then 1
    when status = 'Approved' then 2
    when status = 'Completed' then 3
    end
    // 命名 index
    )", 'features_status_ranking_index');
    });
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    class FeaturesController extends Controller
    {
    public function index()
    {
    $features = Feature::query()
    ->withCount('comments', 'votes')
    ->when(request('sort'), function ($query, $sort) {
    switch ($sort) {
    case 'title': return $query->orderBy('title', request('direction'));
    case 'status': return $query->orderByStatus(request('direction'));
    case 'activity': return $query->orderByActivity(request('direction'));
    }
    })
    ->latest()
    ->paginate();

    return view('features', ['features' => $features]);
    }

    public function scopeOrderByActivity($query, $direction)
    {
    $query->orderBy(
    DB::raw('-(votes_count + (comments_count * 2))'),
    $direction
    );
    }
    }
  • Answer:

    <?php
    class FeaturesController extends Controller
    {
    public function index()
    {
    $features = Feature::query()
    // 取得 comments 以及 votes column counts
    ->withCount('comments', 'votes')
    // 'sort' === 'title' || 'status' || 'activity'
    // request['direction'] 為 desc, 或 asc
    ->when(request('sort'), function ($query, $sort) {
    switch ($sort) {
    case 'title': return $query->orderBy('title', request('direction'));
    case 'status': return $query->orderByStatus(request('direction'));
    case 'activity': return $query->orderByActivity(request('direction'));
    }
    })
    ->latest()
    ->paginate();

    return view('features', ['features' => $features]);
    }
    public function scopeOrderByActivity($query, $direction)
    {
    // 我可以使用 query 中實際或虛擬的 column 來排序
    // - 表示顛倒排序結果
    // 若 votes_count 以及 comments_count 取自虛擬 column, 那是無法做 index 的
    // 可以考慮實際增加兩個欄位, 或是用 virtual column
    $query->orderBy(
    DB::raw('-(votes_count + (comments_count * 2))'),
    $direction
    );
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    class FeaturesController extends Controller
    {
    public function index()
    {
    $features = Feature::query()
    ->withCount('comments', 'votes')
    ->when(request('sort'), function ($query, $sort) {
    switch ($sort) {
    case 'title': return $query->orderBy('title', request('direction'));
    case 'status': return $query->orderByStatus(request('direction'));
    case 'activity': return $query->orderByActivity(request('direction'));
    }
    })
    ->latest()
    ->paginate();

    return view('features', ['features' => $features]);
    }

    public function scopeOrderByStatus($query, $direction)
    {
    $query->orderBy(DB::raw("
    case
    when status = 'Requested' then 1
    when status = 'Approved' then 2
    when status = 'Completed' then 3
    end
    "), $direction);
    }

    }
  • Answer:

    <?php
    class FeaturesController extends Controller
    {
    public function index()
    {
    $features = Feature::query()
    // 取得 comments 以及 votes column counts
    ->withCount('comments', 'votes')
    // 'sort' === 'title' || 'status' || 'activity'
    // request['direction'] 為 desc, 或 asc
    ->when(request('sort'), function ($query, $sort) {
    switch ($sort) {
    case 'title': return $query->orderBy('title', request('direction'));
    case 'status': return $query->orderByStatus(request('direction'));
    case 'activity': return $query->orderByActivity(request('direction'));
    }
    })
    ->latest()
    ->paginate();

    return view('features', ['features' => $features]);
    }
    public function scopeOrderByStatus($query, $direction)
    {
    // 預設 'Requested', 'Approved', 'Completed' 這三個 value 會依照 Alph 方式排序
    // 若想變更排序規則, 可使用以下的方式, 將 value 依照自己想要的順序 return 成數字
    // DB 會依照數字順序排列
    $query->orderBy(DB::raw("
    case
    when status = 'Requested' then 1
    when status = 'Approved' then 2
    when status = 'Completed' then 3
    end
    "), $direction);
    }
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->when(request('sort') === 'town', function ($query) {
    if (config('database.default') === 'mysql' || config('database.default') === 'sqlite') {
    $query->orderByRaw('town is null')
    ->orderBy('town', request('direction'));
    }

    if (config('database.default') === 'pgsql') {
    $query->orderByNullsLast('town', request('direction'));
    }
    })
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }
  • Answer:

    <?php
    public function index()
    {
    $users = User::query()
    ->when(request('sort') === 'town', function ($query) {
    if (config('database.default') === 'mysql' || config('database.default') === 'sqlite') {
    // 如果不加這一行, 那 order by 時會將 null 的顯示在最前面, 加了這一行後, 值為 null 的 row 會被排到最後
    $query->orderByRaw('town is null')
    ->orderBy('town', request('direction'));
    }

    if (config('database.default') === 'pgsql') {
    $query->orderByNullsLast('town', request('direction'));
    }
    })
    ->orderBy('name')
    ->paginate();

    return view('users', ['users' => $users]);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function boot()
    {
    Builder::macro('orderByNullsLast', function ($column, $direction = 'asc') {
    $column = $this->getGrammar()->wrap($column);
    $direction = strtolower($direction) === 'asc' ? 'asc' : 'desc';

    return $this->orderByRaw("$column $direction nulls last");
    });
    }
  • Answer:

    <?php
    public function boot()
    {
    // 建立一個 new query builder method, 名為 orderByNullsLast
    Builder::macro('orderByNullsLast', function ($column, $direction = 'asc') {
    // wrap $column, 因為 $column 為外部帶入參數, 並且又使用 raw method, 為避免 SQL injection, 需使用 wrap
    $column = $this->getGrammar()->wrap($column);
    $direction = strtolower($direction) === 'asc' ? 'asc' : 'desc';

    // 此為 PostgreSQL 語法, 效果為將值為 null 的 column 排在最後
    return $this->orderByRaw("$column $direction nulls last");
    });
    }
Laravel 中, updateOrCreate() 與 MySQL 中的 INSERT … ON DUPLICATE KEY UPDATE 的差異是?

基本上兩者的效果相同, 但實作原理不同, 效能也不同
INSERT … ON DUPLICATE KEY UPDATE 為 MySQL 內建語法, 會透過 unique key 去判斷該 row 是否為 duplicate key
updateOrCreate() 為複數 query 實作而成的 method, 會先 select 比較, 再決定 insert or update
前者的效能較佳

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $books = Book::query()
    ->select('book.*')
    ->join('checkouts', 'checkouts.book_id', '=', 'books.id')
    ->groupBy('books.id')
    ->orderByRaw('max(checkouts.borrowed_date) desc')
    ->withLastCheckout()
    ->with('lastCheckout.user')
    ->paginate();

    return view('books', ['books' => $books]);
    }

    public function scopeWithLastCheckout($query)
    {
    $query->addSelect(['last_checkout_id' => Checkout::select('checkouts.id')
    ->whereColumn('book_id', 'books.id')
    ->latest('borrowed_date')
    ->limit(1),
    ])->with('lastCheckout');
    }
  • Answer:

    <?php
    public function index()
    {
    $books = Book::query()
    ->select('books.*')
    ->join('checkouts', 'checkouts.book_id', '=', 'books.id')
    // 如果不使用 group by, 則或出現多個重複的 book model, 因為每一個 book model 都會對應到多個 checkouts model
    ->groupBy('books.id')
    // 取得最大的 borrowed_date, 即最後借出日期, 在 query 過程中, 每個 row 都會新增一個 column 名為 borrowed_date
    // 其值為 max(checkouts.borrowed_date), 最後 order by 這個 column
    ->orderByRaw('max(checkouts.borrowed_date) desc')
    // dynanmic relation 的概念, 每個 book model 對應多個 checkout model, 但我們只取其中一筆 checkout model
    ->withLastCheckout()
    // 已事先於 checkout model 中定義 user belongsTo relation, 所以直接 eager load 對應的 user model
    ->with('lastCheckout.user')
    ->paginate();

    return view('books', ['books' => $books]);
    }

    public function scopeWithLastCheckout($query)
    {
    // 新增 'last_checkout_id' column
    $query->addSelect(['last_checkout_id' => Checkout::select('checkouts.id')
    // 將 checkout table 與 book table 對應起來
    ->whereColumn('book_id', 'books.id')
    // 以 borrowed_date 排序
    ->latest('borrowed_date')
    // 只取第一筆
    ->limit(1),
    // 取得 last_checkout_id 之後, 便可利用 dynanmic relation 的概念, 取得事先定義的 lastCheckout belongsTo relation
    ])->with('lastCheckout');
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $books = Book::query()
    ->orderBy(User::select('name')
    ->join('checkouts', 'checkouts.user_id', '=', 'users.id')
    ->whereColumn('checkouts.book_id', 'books.id')
    ->latest('checkouts.borrowed_date')
    ->take(1)
    )
    ->withLastCheckout()
    ->with('lastCheckout.user')
    ->paginate();

    return view('books', ['books' => $books]);
    }

    public function scopeWithLastCheckout($query)
    {
    $query->addSelect(['last_checkout_id' => Checkout::select('checkouts.id')
    ->whereColumn('book_id', 'books.id')
    ->latest('borrowed_date')
    ->limit(1),
    ])->with('lastCheckout');
    }
  • Answer:

    <?php
    public function index()
    {
    $books = Book::query()
    // 以 name 排序
    ->orderBy(User::select('name')
    // 透過 join, 取得 user 的所有借書紀錄
    ->join('checkouts', 'checkouts.user_id', '=', 'users.id')
    // 透過 where, 取得每一個 book row 對應到的 data, 如果不使用 where 來把 book.id
    // 以及 checkouts.book_id 做關聯, 那 user name 永遠都會是一樣的
    // 但加上 where 之後, 就可以取得每一個 book 的所有借閱紀錄, 並取得最後借出的那個 user name
    // 最後再 order by 這個 user name
    ->whereColumn('checkouts.book_id', 'books.id')
    // 因為要取得最後借出的 user, 所以要先加以排序
    ->latest('checkouts.borrowed_date')
    // 因為要取得最後借出的 user, 因此只取最後的那一個 user
    ->take(1)
    )
    // 利用 dynanmic relation 的概念, 先新增一個 last_checkout_id 虛擬欄位, 再透過
    // book model 中的 belongs to relation, 經由這個虛擬的 last_checkout_id 取得
    // 最後 checkout 的那一筆 checkout 資料
    ->withLastCheckout()
    // checkout model 中已有先定義與 user 的 belongsTo relation, 因此可以取得與
    // 該 checkout model 相關的 user model
    ->with('lastCheckout.user')
    ->paginate();

    return view('books', ['books' => $books]);
    }

    public function scopeWithLastCheckout($query)
    {
    $query->addSelect(['last_checkout_id' => Checkout::select('checkouts.id')
    // 透過 where, 可取得與每一行 book 相關的 checkout records
    ->whereColumn('book_id', 'books.id')
    // 因為每一個 book model 都會有多筆的 checkout records, 因此須加以排序
    ->latest('borrowed_date')
    // 排序後只取得最新的那一筆, 即最後借出那一筆紀錄, 至此, 只為了取得這一筆 records 的 id
    ->limit(1),
    ])->with('lastCheckout');
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->orderByLastLogin()
    ->withLastLogin()
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeOrderByLastLogin($query)
    {
    $query->orderByDesc(Login::select('created_at')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->take(1)
    );
    }

    public function scopeWithLastLogin($query)
    {
    $query->addSelect(['last_login_id' => Login::select('id')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->take(1),
    ])->with('lastLogin');
    }
  • Answer:

    <?php
    public function index()
    {
    $users = User::query()
    ->orderByLastLogin()
    ->withLastLogin()
    ->paginate();

    return view('users', ['users' => $users]);
    }

    public function scopeOrderByLastLogin($query)
    {
    // 每個 User 都有很多個 Login, 所以在 orderByDesc 當中使用 subquery
    // 取得與該 user 相關的 login records 之後, 再排序, 然後取第一筆
    // 所以會 orderByDesc 每個 user 的最新一筆 login
    $query->orderByDesc(Login::select('created_at')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->take(1)
    );
    }

    public function scopeWithLastLogin($query)
    {
    // 每個 User 都有 many Login, 利用 addSelect 新增一個 column 'last_login_id'
    // 正常來說 User 跟 Login 的 relationship 應該是 User hasMany Login
    // 但這邊為 dynamic model 的概念, 目的為從 hasMany 眾多 records 當中
    // 只 load 想要的那筆資料, 大幅增進效能
    $query->addSelect(['last_login_id' => Login::select('id')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->take(1),
    ])->with('lastLogin');
    }

    public function lastLogin()
    {
    // 正常來說 User 跟 Login 的 relationship 應該是 User hasMany Login
    // 但這邊為 dynamic model 的概念, 目的為從 hasMany 眾多 records 當中
    // 只 load 想要的那筆資料, 大幅增進效能

    return $this->belongsTo(Login::class);
    }
以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->select('users.*')
    ->join('companies', 'companies.user_id', '=', 'users.id')
    ->orderBy('companies.name')
    ->with('company')
    ->paginate();

    return view('users', ['users' => $users]);
    }
  • Answer:
    當需要 orderBy hasOne relationship 的某一個 column, 務必使用 join approach

以下的 Laravel example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    $users = User::query()
    ->select('users.*')
    ->join('companies', 'companies.user_id', '=', 'users.id')
    ->orderBy('companies.name')
    ->with('company')
    ->paginate();

    return view('users', ['users' => $users]);
    }
  • Answer:
    當需要 orderBy belognsTo relationship 的某一個 column, 務必使用 join approach

Laravel 中, 當使用 pagination 時, 務必要使用 orderBy, 為什麼?

因為 pagination 會 offset 並 limit record 數量, 若無使用 orderBy 的話, 很可能每一次的排序都有所不同, 這將導致 pagination 的每一頁的結果是沒有順序性的

Laravel 中, 以下的 migration example code 中, 如果使用 orderBy(‘first_name’)->orderBy(‘last_name’), index 會生效嗎??
  • Example:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    $table->index(['last_name', 'first_name']);
    });
    }
  • Answer:
    不會, 因為 orderBy 順序必須符合 index 的順序

Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function index()
    {
    Auth::login(User::where('name', 'Sarah Seller')->first());

    $customers = Customer::query()
    ->visibleTo(Auth::user())
    ->with('salesRep')
    ->orderBy('name')
    ->paginate();

    return view('customers', ['customers' => $customers]);
    }

    public function scopeVisibleTo($query, User $user)
    {
    if (! $user->is_owner) {
    $query->where('sales_rep_id', $user->id);
    }
    }
  • Answer:
    情境為, 當 user 為 owner 時, 可以看到所有的 customer, 而當 user 為 salesRep 時, 只可看到自己底下的 customer
    主要概念為, 若有這樣的情境限制, 務必將 filter 做在 database layer

Laravel 中, 以下的 migration example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    $table->index(['last_name', 'first_name']);
    });
    }
  • Answer:
    增加一個 compound index, 若是使用 orderBy 時, 順序必須跟 compound index 的順序相同

Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = preg_replace('/[^A-Za-z0-9]/', '', $term).'%';
    $query->whereIn('id', function ($query) use ($term) {
    $query->select('id')
    ->from(function ($query) use ($term) {
    $query->select('users.id')
    ->from('users')
    ->where('users.first_name_normalized', 'like', $term)
    ->orWhere('users.last_name_normalized', 'like', $term)
    ->union(
    $query->newQuery()
    ->select('users.id')
    ->from('users')
    ->join('companies', 'users.company_id', '=', 'companies.id')
    ->where('companies.name_normalized', 'like', $term)
    );
    }, 'matches');
    });
    });
    }
  • Answer:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    // 將 $term 中 "非 A-Za-z0-9" 的內容都替換成 '', 也就是刪去
    $term = preg_replace('/[^A-Za-z0-9]/', '', $term).'%';
    // 之所以不在
    $query->whereIn('id', function ($query) use ($term) {
    $query->select('id')
    ->from(function ($query) use ($term) {
    $query->select('users.id')
    ->from('users')
    // 從 first_name_normalized virtual column 中 query
    // 因為若使用 whereRaw 的 regular expression 語法, 將無法
    // 使用 index
    ->where('users.first_name_normalized', 'like', $term)
    ->orWhere('users.last_name_normalized', 'like', $term)
    ->union(
    $query->newQuery()
    ->select('users.id')
    ->from('users')
    ->join('companies', 'users.company_id', '=', 'companies.id')
    ->where('companies.name_normalized', 'like', $term)
    );
    }, 'matches');
    });
    });
    }
Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    $table->foreignId('company_id')->constrained('companies');
    $table->string('first_name_normalized')->virtualAs("regexp_replace(first_name, '[^A-Za-z0-9]', '')")->index();
    $table->string('last_name');
    $table->string('last_name_normalized')->virtualAs("regexp_replace(last_name, '[^A-Za-z0-9]', '')")->index();
    });
    }
  • Answer:

    <?php
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    // foreignId 為 unsignedBigInteger 的 alias, 而 constrained 會自動為
    // users table 中的 company_id 與 companies table 中的 id column 建立 foreign key index
    $table->foreignId('company_id')->constrained('companies');
    // virtualAs 作用為建立一張虛擬表, 此行 code 會將 first_name column 中的內容
    // 只要不是 a-zA-Z0-9 的都替換成 '', 然後再將內容存到 first_name_normalized 這張
    // 虛擬 table, 並建立 index
    $table->string('first_name_normalized')->virtualAs("regexp_replace(first_name, '[^A-Za-z0-9]', '')")->index();
    $table->string('last_name');
    // 同上
    $table->string('last_name_normalized')->virtualAs("regexp_replace(last_name, '[^A-Za-z0-9]', '')")->index();
    });
    }
Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = $term.'%';
    $query->whereIn('id', function ($query) use ($term) {
    $query->select('id')
    ->from(function ($query) use ($term) {
    $query->select('users.id')
    ->from('users')
    ->where('users.first_name', 'like', $term)
    ->orWhere('users.last_name', 'like', $term)
    ->union(
    $query->newQuery()
    ->select('users.id')
    ->from('users')
    ->join('companies', 'users.company_id', '=', 'companies.id')
    ->where('companies.name', 'like', $term)
    );
    }, 'matches');
    });
    });
    }
  • Answer:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = $term.'%';
    // 最終目的, 是要找到符合 query 條件的 users, 又要讓 index 生效
    // 這邊是 select * from users whereIn('id', nextQuery)
    $query->whereIn('id', function ($query) use ($term) {
    // 這邊是 select id from derived table, 之所以使用 derived table, 是為了
    // 不要讓內外 query 相互依賴, 如果單純在 whereIn 當中使用 subquery 的話, 就會
    // 產生互相依賴, 造成 index 無法生效
    $query->select('id')
    ->from(function ($query) use ($term) {
    $query->select('users.id')
    ->from('users')
    ->where('users.first_name', 'like', $term)
    ->orWhere('users.last_name', 'like', $term)
    // 使用 union 來串接兩句語法
    ->union(
    $query->newQuery()
    ->select('users.id')
    ->from('users')
    ->join('companies', 'users.company_id', '=', 'companies.id')
    ->where('companies.name', 'like', $term)
    );
    // derived table 必須要有一個 alias
    }, 'matches');
    });
    });
    }
Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = $term.'%';
    $query->where(function ($query) use ($term) {
    $query->where('first_name', 'like', $term)
    ->orWhere('last_name', 'like', $term)
    ->orWhereIn('company_id', Company::query()
    ->where('name', 'like', $term)
    ->pluck('id')
    );
    });
    });

    }
  • Answer:
    雖然沒有使用 sub query 造成 query 的數量增加了, 但 users table 因為沒有使用 sub query 的關係而使 index 生效, 雖然 query 數量較多, 但速度卻變快了

Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = $term.'%';
    $query->where(function ($query) use ($term) {
    $query->where('first_name', 'like', $term)
    ->orWhere('last_name', 'like', $term)
    ->orWhereIn('company_id', function ($query) use ($term) {
    $query->select('id')
    ->from('companies')
    ->where('name', 'like', $term);
    });
    });
    });
    }
  • Answer:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    // str_getcsv 會將 ' ' (空白) 分出來, " (double quote) 也分出來, return 一個 array
    // 再把這個 array 使用 each 迭代
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    // prefix 不用 %, 因為 prefix % 不適用於 index
    $term = $term.'%';
    $query->where(function ($query) use ($term) {
    $query->where('first_name', 'like', $term)
    ->orWhere('last_name', 'like', $term)
    // 這邊不使用 orWhereHas, 因為 SQL 語法會關聯兩張表,
    // 這個 dependency 會造成 company_name 不適用 index
    // 因此使用 whereIn 來避開此 dependency
    ->orWhereIn('company_id', function ($query) use ($term) {
    $query->select('id')
    ->from('companies')
    ->where('name', 'like', $term);
    });
    });
    });
    }
Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function scopeSearch($query, string $terms = null)
    {
    collect(str_getcsv($terms, ' ', '"'))->filter()->each(function ($term) use ($query) {
    $term = $term.'%';
    $query->where(function ($query) use ($term) {
    $query->where('first_name', 'like', $term)
    ->orWhere('last_name', 'like', $term)
    ->orWhereHas('company', function ($query) use ($term) {
    $query->where('name', 'like', $term);
    });
    });
    });
    }
  • Answer:
    str_getcsv 會把空白隔開的當成一個 value, " 內的也當成一個 value, 再把各個 value 變成 $term 下去 query

Laravel 中, 以下的 example code 的意思是?
  • Example:

    <?php
    public function show(Feature $feature)
    {
    $feature->load('comments.user');
    $feature->comments->each->setRelation('feature', $feature);

    return view('feature', ['feature' => $feature]);
    }
  • Answer:

    <?php
    public function show(Feature $feature)
    {
    $feature->load('comments.user');

    // 這裡手動的為每個 comments 建立 relation 名為 feature, 代入當前已載入 memory 的 $feature
    // 如此一來, 在上一行 eager load comments 之後, 每一個 comments 都還會有在上一行已經完成
    // eager load 的 feature relation, 那當我們執行 $feature->comments->feature->comments
    // 時就不會重複 SQL 語法, 也不會重複的加載 model
    // 當執行 $feature->comments->feature->comments 時, 如果不使用 eager loading, 會有
    // n+1 issue, 但如果使用 with('comments.feature.comments') 的話, 則會 n*n 的加載不必要
    // 的 model
    // 這樣的 relation 又稱為 circular relation
    $feature->comments->each->setRelation('feature', $feature);

    return view('feature', ['feature' => $feature]);
    }
Laravel 中, 動態 model 的技術概念是?

假設 User model 跟 Login model 的 relation 為 hasMany, 但我在取得 User model 時, 我只想要取得最近 login 的 Login model
所以先用 subquery 來取得一個虛擬的 column 為 login_id, 這時再用這個 login_id 透過 belongsTo 的 relation 取得單筆 model

Laravel 中, 假設今天我想要取得 hasMany relation 中的一筆 record, 這時如果使用 with() 的話會 load 所有的 model, 不使用 with() 的話又會執行很多次 query, 有什麼好的解法?
  • Example:

    <?php
    $users = User::query()
    ->with('login')
    ->orderBy('name')
    ->paginate();
  • Answer:

    <?php
    // 使用 subquery, 從 hasMany relation 中只 query 出我們要的那筆資料, 再將這筆資料變成主 query 中的一個 column
    $users = User::query()
    ->addSelect(['last_login_at' => Login::select('created_at')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->take(1)
    ])
    ->orderBy('name')
    ->paginate();
Laravel 中, 如果使用 composer 安裝套件時, 超過容許的 memory 限制, 可以使用哪個 flag 讓容許 memory 無上限?
COMPOSER_MEMORY_LIMIT=-1
以下的 Laravel 程式碼的意思是?
  • Example:

    <?php
    $ip = data_get($this->pendingWalletSettlement->settleable, 'depositDetail.client_ipv4')
  • Answer:

    <?php
    // 從 settleable 得到的 model 裏頭, 取得他的 relation depositDetail 的 attribute client_ipv4
    $ip = data_get($this->pendingWalletSettlement->settleable, 'depositDetail.client_ipv4')
解釋以下的 Laravel example
  • Example:

    <?php
    public function scopeOf(Builder $query, ...$models)
    {
    foreach ($models as $model) {
    /** @var $model Model */
    $query->where($model->getForeignKey(), $model->getKey());
    }
    }
  • Answer:

    <?php
    // 可帶入多個 model
    public function scopeOf(Builder $query, ...$models)
    {
    // loop 每個 model
    foreach ($models as $model) {
    /** @var $model Model */
    // 取得該 model 的 `foreign key name`, 以及 `primary key value`
    // 所以可以在目標 model 使用其他 model 為條件來 query
    $query->where($model->getForeignKey(), $model->getKey());
    }
    }
安裝完新的 PHP Extension, 記得要做些什麼事? 重啟 PHP, 重啟 Web server, 若使用 valet, 記得重啟 valet
在 Laravel 中, 如何確認 model 是否已經被改過?
model->isDirty()
// true or false
在 Laravel 中, 如何得到與該 model 相關的 table 的名字?
model->getTable()
// string
在 Laravel 中, 下面的代碼是什麼意思? 若 $request 中有指定的值, 如 ‘row’, 則使用該值, 若該值不存在, 則使用第二個自己指定的值
$row = request('row', (new Deposit)->getPerPage());
在 Laravel 中, 下面的代碼是什麼意思?
foreach (
['status', 'system_order_number', 'order_number', 'user_card_number', 'system_card_number'] as
$availableFilterKey
) {
$deposits->when($request->{$availableFilterKey},
function (Builder $builder, $filterValue) use ($availableFilterKey) {
$builder->where($availableFilterKey, $filterValue);
});
}
  • foreach 依序 loop array 當中的每一個 value
  • $availableFilterKey 這裡表示 array 當中的 value
  • when 表示條件句, [boolean, executeIfTrue, executeIfFalse]
  • $builder 代表 $deposit
  • $filterValue 代表 $request->availableFilterKey
  • 參考文件
在 Laravel 中, 如何取得 query builder?
$query = yourModel::query();
在 Laravel 中, ** 代表什麼意思?
power operator, 如果是 2 ** 16, 代表 2 * 2 * .. 16 次
在 Laravel 中, 如何取得 parent 的 method?
# 如果沒複寫, 直接呼叫即可
$this->method

# 如果有複寫, 也是直接呼叫
$this->method

# 除非你有複寫, 但你要沒複寫的版本
parent::method

參考出處: https://stackoverflow.com/questions/11237511/multiple-ways-of-calling-parent-method-in-php
Laravel 的時區設定檔位置在?
config 中的 app.php
資料庫欄位 int, 什麼是 signed 跟 unsigned?
signed 的數值範圍橫跨正負數, unsigned 只有正數, 且為 signed 的兩倍, 但最小的負數為 0
可參考文件如下: https://dev.mysql.com/doc/refman/8.0/en/integer-types.html
Laravel 當中, 以下的語法代表什麼意思? 列出 $users query builder 的 query 語法以及帶入的變數
<?php
dd($users->toSql(), $users->getBindings());
Laravel 當中, 以下代碼代表什麼意思?$user->address 可獲得時, 取值, 不可獲得時, 回傳 null
<?php
return optional($user->address)->street;
Laravel 當中, 哪裡可以設定 queue 的名字?
config 資料夾中的 queue
Laravel 當中, 以下的 agent_id 驗證 exists 邏輯代表什麼意思? 在 users table 當中, 帶入的 agent_id 必須要跟 table 裡頭的 id 相同, 且 role_id 必須得跟 Role::AGENT 相同
<?php
request()->validate([
'q' => 'nullable | string | max:255',
'row' => 'nullable | int | digits_between:1,3',
'agent_id' => 'nullable | exists:users,id,role_id,'.Role::AGENT,
]);
以下的 Laravel 程式碼代表什麼意思?
<?php
$users = User::merchant()
->with('owner')
->when($q, function ($query, $q) {
return $query->where('username', 'LIKE', '%'.$q.'%')
->orWhere('name', 'LIKE', '%'.$q.'%')
->orWhere('email', 'LIKE', '%'.$q.'%');
});

public function scopeMerchant(Builder $query)
{
return $query->where('role_id', Role::MERCHANT);
}
取得 User model 的 merchant scope, eager load owner relation, 當 $q 不為 null, 執行 closure, 接上 closure 中的 query builder, 在 username, name, email 三個欄位中模糊搜尋 $q
以下的 Laravel 程式碼的 appends 邏輯是什麼? 在 Laravel 的分頁模式中, 若未將全部來自於前端的 query 帶入, 那分頁的 url 將會缺少必要的 query, 等於只有第一頁會有 帶入的 query 結果
<?php
return new UserCollection($users->paginate($row)->appends(request()->query->all()));
以下的 Laravel 程式碼中的 latest 代表什麼意思? 以 id 排序
<?php
$transactions = auth()->user()->transactions()
->whereBetween('created_at', [$startedAt, $endedAt])
->latest('id')
->with('wallet')
->paginate()
->appends($request->query->all());
以下的 Laravel 程式碼中的 fill 代表什麼意思? 將值注入 userBankCard model, 帶入參數可以是一個 array
<?php
$userBankCard->fill(
$request->only(
'card_holder_name', 'card_number', 'bank_name'
)
);
以下的 Laravel 程式碼中, 為什麼要使用 collect function? 這樣如果前端帶錯, 帶成 string 的話, 會先將 string 轉成 collection, 再轉成 array
<?php
$userBankCards = UserBankCard::when(request()->q, function ($query, $q) {
$query->where(function ($query) use ($q) {
$query->where('card_holder_name', 'LIKE', '%' . $q . '%')
->orWhere('card_number', 'LIKE', '%' . $q . '%')
->orWhere('bank_name', 'LIKE', "%$q%");
});
})
->when(request()->status, function ($query, $status) {
$query->whereIn('status', collect($status)->toArray());
})
->where('user_id', auth()->user()->getKey())
->paginate($row)
->appends(request()->query->all());
以下的 Laravel 程式碼代表什麼意思?
<?php
$depositStats = Deposit::whereBetween('created_at', [$startDate, $endDate])->where('status', Deposit::STATUS_SUCCESS)->groupBy('system_bank_card_type')->get([
'system_bank_card_type',
DB::raw('SUM(amount) AS total_amount, SUM(fee) AS total_fee, COUNT(id) AS total_count')
])->keyBy('system_bank_card_type');
取得 Deposit model, 以帶入日期過濾, 以 SUCCESS status 過濾, 以 system_bank_card_type 分類, 在取得四個值, 分別是 system_bank_card_type, total_amount, total_fee, total_count, 若照預設, 會是一個 collection 裡面有兩個 model, index 為 0 跟 1, 使用 keyBy 來將 0 跟 1 依照 system_bank_card_type 做區分, 所以會變成一個 collection 裡頭有兩個 model, 以 system_bank_card_type 做區分
以下的 Laravel 程式碼為什麼要使用 first? 因為該 query 撈出來後, 只會有一筆 model, 如果是用 get 的話, 會是一個 collection 裡有一個 model, 所以直接使用 first() 即可
<?php
$withdrawStat = Withdraw::whereBetween('created_at', [$startDate, $endDate])
->where('status', Withdraw::STATUS_SUCCESS)
->first([
DB::raw('SUM(amount) AS total_amount, SUM(fee) AS total_fee, COUNT(id) AS total_count')
]);
以下的 Laravel 程式碼中, keyBy 的用途是? 如果不使用 keyBy 的話, 正常來說一個 collection 裡頭有多個 model 會以默認 index, 0, 1, 2 …, keyBy 可以使用指定的 key 來給 model 分組, 在這個例子中, 就是以 model 下的 slug 欄位的值做分組
<?php
$wallets = auth()->user()->wallets()->get()->keyBy('slug');
以下的 Laravel 程式碼中, data_get 的用途是? $depositStats 結構像是這樣 $depositStats = ['BankCard::TYPE_FEE' => ['total_count', 'total_amount', 'total_count', 'total_amount']], data_get 可以取得一個 collection 裡頭的巢狀 array 值
<?php
return response()->json([
'data' => [
'fee_wallet_deposit_success_count' => data_get($depositStats, [BankCard::TYPE_FEE, 'total_count'], 0),
'fee_wallet_deposit_success_amount' => data_get($depositStats, [BankCard::TYPE_FEE, 'total_amount'], 0) / 100, // todo remove hard code
'withdraw_wallet_deposit_success_count' => data_get($depositStats, [BankCard::TYPE_WITHDRAW, 'total_count'], 0),
'withdraw_wallet_deposit_success_amount' => data_get($depositStats, [BankCard::TYPE_WITHDRAW, 'total_amount'], 0) / 100, // todo remove hard code
'withdraw_success_count' => $withdrawStat->total_count ?? 0,
'withdraw_success_amount' => ($withdrawStat->total_amount ?? 0) / 100, // todo remove hard code
'fee_wallet_balance' => data_get($wallets, [User::SLUG_FEE_WALLET, 'balanceFloat'], 0),
'withdraw_wallet_balance' => data_get($wallets, [User::SLUG_WITHDRAW_WALLET, 'balanceFloat'], 0),
],
]);
以下的 Laravel 程式碼代表什麼意思? 定義一個 unique rule, 並且將範圍限定在特定的 user 上, 代表不同 user 之間的訂單不需要 unique
<?php
$orderNumberUniqueRule = Rule::unique($withdrawTable, 'order_number')->where(function ($query) use ($user) {
$query->where('user_id', $user->getKey());
});
以下的 Laravel function 的作用是什麼?
  1. 將 request 裡的參數除了 sign 之外都調出來
  2. 排列這些 key
  3. 首先使用 http_build_query function 針對剛剛的參數來產生一組 url 加密的字串, 然後將這字串與 user 的 secret_key 欄位內的值相串, 然後使用 url 解密這一整個字串, 最後再使用 md5 處理取得 hash 值, 我們比對這個值跟帶進來的 sign 有沒有一樣, 如果不一樣就是不合法
  4. 唯有知道 secret_key 的雙方可以對內容加解密, 而經由這樣的加解密驗證, 確保 request 的內容再傳送過程中未被串改
    <?php
    private function signValid(Request $request, $secretKey)
    {
    $allParametersExceptSign = $request->except('sign');

    ksort($allParametersExceptSign);

    return strcasecmp(
    md5(urldecode(http_build_query($allParametersExceptSign)) . $secretKey),
    $request->sign
    ) === 0;
    }
以下的 Laravel function 的作用是什麼?
  1. 宣告 lock-key 以及持有時間
  2. 嘗試取得 lock-key, 如果不可得, 持續嘗試五秒
  3. 用 transaction 實作, 若有任何錯誤皆返回
  4. 如果無法取得 lock-key, 返回錯誤
  5. 回返錯誤訊息
  6. 如果 lock-key 還被持有中, 釋放 lock-key
    <?php
    public function lock(User $user, $action)
    {
    $lock = Cache::lock($user->lockKey(), 10);

    try
    {
    $lock->block(5);

    return DB::transaction($action);

    } catch (LockTimeoutException $e)
    {
    abort(Response::HTTP_CONFLICT, '请稍候再试');
    } finally
    {
    optional($lock)->release();
    }
    }
以下的 Laravel 程式碼中, balance 是扣款前還是扣款後? 扣款前, 因為 $transaction 還沒被執行完畢
<?php
$transaction = $user->withdrawFloat($request->input('amount'), [
'before_balance' => $user->balance,
]);
以下的 Laravel 程式碼是什麼意思呢? 將資料存入 mysql 中的 json 欄位
<?php
$deposit->user_bank_meta = (object)[
'subbranch' => $userBankCard->subbranch,
'province' => $userBankCard->province,
'city' => $userBankCard->city,
];
下面的 Laravel 程式碼是什麼意思? 將檔案存在 $deposit->getTable(), 檔名為 $deposit->system_order_number, 使用 filesystem.cloud, 可設為 s3
<?php
$request->file('payment_instrument')
->storeAs($deposit->getTable(), $deposit->system_order_number, config('filesystems.cloud'));
下面的 Laravel Requests 代表什麼意思? 使用 captcha 的 extension captcha_api 來驗證, 因為該驗證器一定需要一個 key, 如果在 validation 期間 key 為 null, 那直接就回 500 了, 所以這邊處理, 當沒有 captcha_key 時, 給一個隨機 10 碼, 這樣會驗不過(key 沒帶原本就應該驗不過), 但是不會 500
<?php
public function rules()
{
return [
'username' => 'required_without:email|string',
'email' => 'required_without:username|email',
'password' => 'required|string',
'captcha_key' => 'required',
'captcha' => 'required|captcha_api:'.($this->request->get('captcha_key') ?? Str::random(10)),
];
}
以下的 Laravel 程式碼中, where 內為什麼只有一個參數?

Laravel 中, 如果 request 中的 parameter 與資料庫中的欄位名稱相同, 就可以直接用這種方式 query

<?php
$bankCard = BankCard::where($request->only('card_number'))
->withTrashed()
->first() ?? BankCard::create($data);

以下的 Laravel 程式碼是什麼意思?
1. query 出 card_number 的 model
2. withTrashed 代表強制顯示已被 soft deleted 的 model
3. 取出第一筆
4. 若無結果, 則根據輸入的資料建立一張卡
5. 更新 bankCard
6. 若 bankCard 為 soft deleted, 解除它
<?php
$bankCard = BankCard::where($request->only('card_number'))
->withTrashed()
->first() ?? BankCard::create($data);

$bankCard->update($data);
$bankCard->restore();
在 Laravel 中, 如何從一個 collection 當中取得其中一個 model, 而該 model 中的 price 欄位的值是在這個 collection 的所有 model 之中最小或最大的?
<?php
$min = $data->where('price', $data->min('price'))->first();
// ['name' => 'test', 'price' => 10]
$max = $data->where('price', $data->max('price'))->first();
// ['name' => 'test', 'price' => 600]
在 Laravel 當中, 如何在一個 collection 裡放入多個 model ?
<?php
collection->push($model)
以下的 Laravel 程式碼代表什麼意思?
1. 帶入欄位的名稱需與資料庫的欄位一樣
2. 依序檢查指定的欄位
3. 若 request 中的該欄位是有值的, 並且該值與目前資料庫中的值是不同的, 這代表有變更產生
4. 將變更的 key 跟 value 放入空的 collection $updateAttributes 中
5. 如果這個 collection 含有 card_number, 這代表卡號變更了, 將 balance => 0 放入 updateAttributes
6. 如果 updateAttributes 是存在的, 開始更新
<?php
$updatedAttributes = collect();

foreach (['card_holder_name', 'card_number', 'bank_name', 'type', 'auto_withdraw'] as $attribute) {
if (!is_null($request->$attribute) && ($request->$attribute != $bankCard->$attribute)) {
$updatedAttributes = $updatedAttributes->merge([$attribute => $request->$attribute]);
}
}

if ($updatedAttributes->has('card_number')) {
$updatedAttributes = $updatedAttributes->merge(['balance' => 0]);
}

if ($updatedAttributes->isNotEmpty()) {
$bankCard->update($updatedAttributes->toArray());
}
Laravel 中, Model 的命名通常是單數還是複數?

單數

操作 Resource 的 controller, 通常命名的規則是?

該 Resource 對應的名稱, 如 IpController

Laravel 中的變數命名習慣是?

camel case

Laravel 中, blade view 的命名習慣是?

會以資料夾做區分, 像是 withdraws/index.blade.php、withdraws/store.blade.php

Laravel 中, 如何將 namespace, prefix, middleware 同時作用到複數的 route 上?
<?php
Route::group([
'namespace' => 'Worker',
'prefix' => 'worker',
'middleware' => 'check.worker.token',
], function () {
Route::apiResource('working-tasks', 'WorkingTaskController')->only('store', 'update');
Route::apiResource('bank-cards', 'BankCardController')->only('update');

Route::post('captcha-cracks', 'CrackCaptchaController');
});
Laravel 中, 可否在 route 的 group 內再使用一個 group?

可以的

Laravel 中, 巢狀內的 route group 的屬性會不會繼承外層的 group 的屬性?

會的, 像是

<?php
Route::group([
'middleware' => ['auth']
], function () {
Route::get('me', 'AuthController@me');

Route::group([
'namespace' => 'Admin',
'prefix' => 'admin',
'middleware' => ['check.role.admin', 'check.source.admin'],
], function () {
Route::post('users/{user}/reset-password', 'UsersController@resetPassword');
Route::post('users/{user}/reset-withdraw-password', 'UsersController@resetWithdrawPassword');
Route::post('users/{user}/reset-google2fa-secret', 'UsersController@resetGoogle2faSecret');
Route::post('users/{user}/reset-secret-key', 'UsersController@resetSecretKey');
Route::put('users/{user}/delete-group', 'UsersController@deleteGroup')
->where(['user' => '[0-9]+']);
});
});

以下的 Laravel 程式碼中, 邏輯是怎麼樣的?
如果環境是在 production 的話, 檢查來源 ip, 來源 ip 可能有很多個, 取最後一個代表 client, 若不存在則拒絕存取
<?php
public function handle($request, Closure $next)
{
if (app()->environment(['production'])) {
abort_if(!IPs::where('address', Arr::last($request->ips()))->exists(), Response::HTTP_UNAUTHORIZED,
'Invalid source');
}

return $next($request);
}
以下的兩段 Laravel 在 Resource 中的程式碼, 有什麼差異?
  1. <?php
    'withdraws' => $this->whenLoaded('withdraws', Withdraw::collection($this->withdraws))
  2. <?php
    'withdraws' => $this->whenLoaded('withdraws', function () {
    return Withdraw::collection($this->withdraws);
    })

    第一個 block 中, PHP 會先去執行作為參數帶入的 $this->withdraws, 再將結果帶入 whenLoaded, 這便符合了 whenLoaded 的 relation 載入條件, 所以依然會將當前 resource 下的 relation 顯示出來, 實際運行上因為會先執行 $this->withdraws, 因此也會造成效能上的浪費

第二個 block 中, closure 在被呼叫之前, PHP 並不會去解析它, 所以會先執行 whenLoaded 函式, 如果條件吻合, 才會執行 closure, 所以不會去執行 $this->withdraws, 自然 whenLoaded 的條件就不會吻合, resource 中也就不會多撈一層

Laravel 中, 如何將 array 存到資料庫?
  • 資料庫類別為 json

    <?php
    Schema::table('users', function (Blueprint $table) {
    $table->json('iAmarray')->nullable();
    });
  • 存到資料庫前, 先使用 json_encode

    <?php
    data = json_encode($iAmArray);
  • 在 model 加入

    <?php
    protected $casts = [
    'iAmArray' => 'array'
    ];
當我在 Route 當中使用 apiResource 如下, 自動帶入 controller 的 model binding 的變數名稱為?

sub_account

<?php
Route::apiResource('sub-accounts', 'SubAccountsController')->only(['store', 'update']);

Laravel 中, 當我使用 scheduler, 腳本內的 user 務必要使用?

與 webserver 同一個 user

以下的 Laravel 程式碼的邏輯是?
  • 程式碼:

    <?php
    $canSeeSecretKey = optional(auth()->user())->isAdmin() || $this->is(auth()->user());
  • Answer:

    • 如果 auth()->user() 的身份是 admin 的話
    • 如果被帶入 resource 中的 model 的身份跟 auth()->user() 是同一個人的話(代表本人)
以下的 Laravel 程式碼的作用是?
  • code:

    <?php
    public static function depositTypeText()
    {
    return collect((new ReflectionClass(__CLASS__))->getConstants())
    ->filter(
    function ($value, $key) {
    return Str::startsWith($key, 'TYPE_DEPOSIT');
    }
    )
    ->mapWithKeys(
    function ($value, $key) {
    return [$value => $key];
    }
    );
    }
  • Answer:

    • RefectionClass: 取得指定 class 中的資料
    • __class__: 代表當前 class
    • getConstants: 取得 constants
    • filter: 只取符合條件的 key
    • mapWithKeys: 取得符合條件的 key/value pair
以下的 Laravel 程式碼的邏輯是?
  • Example:

    <?php
    if ($endedAt->diffInDays($startedAt) > 31) {
    $request->merge(
    [
    'ended_at' => (clone $startedAt)->addDays(31)->format('Y-m-d H:i:s'),
    ]
    );
    }
  • Answer:
    如果 $endedAt 跟 $startedAt 相差大於 31 天, 那就把範圍定在最多相差 31 天

PHPSTORM 學習筆記 PHP 學習筆記

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×