學幾個 Laravel Eloquent 方法和技巧

我第一次尋找所謂的 Laravel 框架的時候,個人其中一個目標就是要找:利用最簡單的操做數據庫的方法。後來目標就停在了 Eloquent ORM 上。php

今天說一說 Eloquent ORM 的一些不易被發現和使用的方法。laravel

1. 遞增和遞減函數

平時這麼寫:sql

$article = Article::find($article_id);
$article->read_count++;
$article->save();
複製代碼

利用 increment 函數數據庫

$article = Article::find($article_id);
$article->increment('read_count');
複製代碼

固然能夠傳入數字,不僅是隻增減 1:express

Article::find($article_id)->increment('read_count');
Article::find($article_id)->increment('read_count', 10); // +10
Product::find($produce_id)->decrement('stock'); // -1
複製代碼

咱們來看看源代碼是怎麼實現的:數組

/** * Increment a column's value by a given amount. * * @param string $column * @param int $amount * @param array $extra * @return int */
public function increment($column, $amount = 1, array $extra = []) {
    if (! is_numeric($amount)) {
        throw new InvalidArgumentException('Non-numeric value passed to increment method.');
    }

    $wrapped = $this->grammar->wrap($column);

    $columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra);

    return $this->update($columns);
}

/** * Decrement a column's value by a given amount. * * @param string $column * @param int $amount * @param array $extra * @return int */
public function decrement($column, $amount = 1, array $extra = []) {
    if (! is_numeric($amount)) {
        throw new InvalidArgumentException('Non-numeric value passed to decrement method.');
    }

    $wrapped = $this->grammar->wrap($column);

    $columns = array_merge([$column => $this->raw("$wrapped - $amount")], $extra);

    return $this->update($columns);
}
複製代碼

主要利用 $this->grammar 解析 $column 字段,轉變爲可執行的 sql 語句。app

/** * Wrap a value in keyword identifiers. * * @param \Illuminate\Database\Query\Expression|string $value * @param bool $prefixAlias * @return string */
public function wrap($value, $prefixAlias = false) {
    if ($this->isExpression($value)) {
        return $this->getValue($value);
    }

    // If the value being wrapped has a column alias we will need to separate out
    // the pieces so we can wrap each of the segments of the expression on it
    // own, and then joins them both back together with the "as" connector.
    if (strpos(strtolower($value), ' as ') !== false) {
        return $this->wrapAliasedValue($value, $prefixAlias);
    }

    return $this->wrapSegments(explode('.', $value));
}

/** * Wrap the given value segments. * * @param array $segments * @return string */
protected function wrapSegments($segments) {
    return collect($segments)->map(function ($segment, $key) use ($segments) {
        return $key == 0 && count($segments) > 1
                        ? $this->wrapTable($segment)
                        : $this->wrapValue($segment);
    })->implode('.');
}

/** * Wrap a single string in keyword identifiers. * * @param string $value * @return string */
protected function wrapValue($value) {
    if ($value !== '*') {
        return '"'.str_replace('"', '""', $value).'"';
    }

    return $value;
}
複製代碼

注: $grammer 是個抽象類,項目會根據不一樣的數據庫,而採用不一樣的 $grammer 繼承類來實現查詢功能框架

最後一個參數是 $extra,由於 increment 函數最後會執行 update() 方法,因此能夠把額外須要操做數據的語句放在 $extra 數組中。ide

2. WhereX

這裏的 where 是前綴的做用,X表示的是咱們的字段名,能夠簡化咱們的查詢寫法,平時都是這麼寫的:函數

$users = User::where('approved', 1)->get();
複製代碼

簡便的寫法:

$users = User::whereApproved(1)->get();
複製代碼

具體實現主要利用 __call 方法。

public mixed __call ( string name , arrayarguments )

public static mixed __callStatic ( string name , arrayarguments )

在對象中調用一個不可訪問方法時,__call() 會被調用。

在靜態上下文中調用一個不可訪問方法時,__callStatic() 會被調用。

Query/Builder.php 中能夠看出:

/** * Handle dynamic method calls into the method. * * @param string $method * @param array $parameters * @return mixed * * @throws \BadMethodCallException */
public function __call($method, $parameters) {
    if (static::hasMacro($method)) {
        return $this->macroCall($method, $parameters);
    }

    if (Str::startsWith($method, 'where')) {
        return $this->dynamicWhere($method, $parameters);
    }

    $className = static::class;

    throw new BadMethodCallException("Call to undefined method {$className}::{$method}()");
}
複製代碼

where 查詢方法都會調用函數:

return $this->dynamicWhere($method, $parameters);
複製代碼
/** * Handles dynamic "where" clauses to the query. * * @param string $method * @param string $parameters * @return $this */
public function dynamicWhere($method, $parameters) {
    $finder = substr($method, 5);

    $segments = preg_split(
        '/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE
    );

    // The connector variable will determine which connector will be used for the
    // query condition. We will change it as we come across new boolean values
    // in the dynamic method strings, which could contain a number of these.
    $connector = 'and';

    $index = 0;

    foreach ($segments as $segment) {
        // If the segment is not a boolean connector, we can assume it is a column's name
        // and we will add it to the query as a new constraint as a where clause, then
        // we can keep iterating through the dynamic method string's segments again.
        if ($segment !== 'And' && $segment !== 'Or') {
            $this->addDynamic($segment, $connector, $parameters, $index);

            $index++;
        }

        // Otherwise, we will store the connector so we know how the next where clause we
        // find in the query should be connected to the previous ones, meaning we will
        // have the proper boolean connector to connect the next where clause found.
        else {
            $connector = $segment;
        }
    }

    return $this;
}
複製代碼

繼續看 addDynamic 函數:

/** * Add a single dynamic where clause statement to the query. * * @param string $segment * @param string $connector * @param array $parameters * @param int $index * @return void */
protected function addDynamic($segment, $connector, $parameters, $index) {
    // Once we have parsed out the columns and formatted the boolean operators we
    // are ready to add it to this query as a where clause just like any other
    // clause on the query. Then we'll increment the parameter index values.
    $bool = strtolower($connector);

    $this->where(Str::snake($segment), '=', $parameters[$index], $bool);
}
複製代碼

最後回到了 $this->where(Str::snake($segment), '=', $parameters[$index], $bool); 常規的 where 語句上;

同時,這過程咱們能夠發現 whereX 方法,不只能夠傳入一個字段,並且還能夠傳入多個字段,用「And」或者 「Or」鏈接,且字段首字母用大寫「A~Z」。

3. XorY methods

在平時有太多的寫法都是,先查詢,再判斷是否存在,而後再決定是輸出,仍是建立。

如:

$user = User::where('email', $email)->first();
if (!$user) {
  User::create([
    'email' => $email
  ]);
}
複製代碼

一行代碼解決:

$user = User::firstOrCreate(['email' => $email]);
複製代碼

:這裏還有一個函數 firstOrNewfirstOrCreate類似,看代碼:

/** * Get the first record matching the attributes or instantiate it. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model */
    public function firstOrNew(array $attributes, array $values = []) {
        if (! is_null($instance = $this->where($attributes)->first())) {
            return $instance;
        }

        return $this->newModelInstance($attributes + $values);
    }

    /** * Get the first record matching the attributes or create it. * * @param array $attributes * @param array $values * @return \Illuminate\Database\Eloquent\Model */
    public function firstOrCreate(array $attributes, array $values = []) {
        if (! is_null($instance = $this->where($attributes)->first())) {
            return $instance;
        }

        return tap($this->newModelInstance($attributes + $values), function ($instance) {
            $instance->save();
        });
    }
複製代碼

主要區別場景在於:若是是在已有 $attributes 下查找並建立的話,就能夠用 firstOrCreate。若是當咱們須要先查詢而後再對 model 進行後續的操做的話,應使用 firstOrNew 方法,將 save 保存數據庫操做放在最後;以避免重複執行 save()方法。

4. find()

find() 函數經過主鍵獲取數據,平時都是獲取單數據,其實傳入的參數還能夠是「主鍵數組」,獲取多 models。

$users = User::find([1,2,3]);
複製代碼

咱們查看它的函數實現:

/** * Find a model by its primary key. * * @param mixed $id * @param array $columns * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null */
    public function find($id, $columns = ['*']) {
        if (is_array($id) || $id instanceof Arrayable) {
            return $this->findMany($id, $columns);
        }

        return $this->whereKey($id)->first($columns);
    }
複製代碼

首先判斷的是 id 是否是 array,若是是的話,則執行 findMany 函數:

/** * Find multiple models by their primary keys. * * @param \Illuminate\Contracts\Support\Arrayable|array $ids * @param array $columns * @return \Illuminate\Database\Eloquent\Collection */
    public function findMany($ids, $columns = ['*']) {
        if (empty($ids)) {
            return $this->model->newCollection();
        }

        return $this->whereKey($ids)->get($columns);
    }
複製代碼

獲取的結果是一個 Collection 類型。

總結

Laravel 框架有不少地方值得咱們去研究,看 Laravel 是如何封裝方法的。Eloquent ORM 還有不少方法能夠一個個去看源代碼是怎麼實現的。

本文內容更多來自:laravel-news.com/eloquent-ti…

還有不少函數均可以拿出來分析,如:

Relationship with conditions and ordering

public function orders() {
    return $this->hasMany('App\Order');    
}
複製代碼

其實咱們能夠在獲取多訂單的同時,加入篩選語句和排序。如,獲取已支付並按更新時間倒序輸出:

public function paidOrders() {
    return $this->hasMany('App\Order')->where('paid', 1)->orderBy('updated_at');
}
複製代碼

「未完待續」

相關文章
相關標籤/搜索