When I first looked for the so-called Laravel framework, one of my goals was to find the easiest way to manipulate a database. And then the goal stopped at the Eloquent ORM.

And today we’re going to talk about some of the hardest to find and use Eloquent ORM methods:

1. Increasing and decreasing functions

Usually write like this:

$article = Article::find($article_id);
$article->read_count++;
$article->save();
Copy the code

Increment function

$article = Article::find($article_id);
$article->increment('read_count');
Copy the code

Of course you can pass in numbers, not just increments or subtractions of 1:

Article::find($article_id)->increment('read_count');
Article::find($article_id)->increment('read_count'.10); / / + 10
Product::find($produce_id)->decrement('stock'); // -1
Copy the code

Let’s see how the source code works:

/**
 * 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);
}
Copy the code

$this->grammar is used to parse the $column field into an executable SQL statement.

/**
 * 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 ! = =The '*') {
        return '"'.str_replace('"'.'" "', $value).'"';
    }

    return $value;
}
Copy the code

Note: $Grammer is an abstract class, and the project will use a different $Grammer inherited class to implement the query function depending on the database

The last parameter is $extra. Because increment finally executes the update() method, you can put additional statements that need to manipulate data in the $extra array.

2. WhereX

Where = ‘X’; where = ‘X’; where = ‘X’;

$users = User::where('approved'.1)->get();
Copy the code

A simple way to write:

$users = User::whereApproved(1)->get();
Copy the code

The implementation mainly uses the __call method.

public mixed __call ( string arguments )

public static mixed __callStatic ( string arguments )

When an unreachable method is called from an object, __call() is called.

__callStatic() is called when an unreachable method is called in a static context.

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}()");
}
Copy the code

All where query methods call the function:

return $this->dynamicWhere($method, $parameters);
Copy the code
/**
 * 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;
}
Copy the code

Continue with the addDynamic function:

/**
 * 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);
}
Copy the code

$this->where(Str::snake($segment), ‘=’, $parameters[$index], $bool); On regular WHERE statements;

At the same time, we discovered that the whereX method can pass not just one field, but multiple fields, concatenated with “And” Or “And starting with A capital” A to Z.”

3. XorY methods

In ordinary times, there are too many writing methods are, first query, then determine whether there is, and then decide whether to output, or create.

Such as:

$user = User::where('email', $email)->first();
if(! $user) { User::create(['email' => $email
  ]);
}
Copy the code

One line of code to solve:

$user = User::firstOrCreate(['email' => $email]);
Copy the code

Note: here is another function firstOrNew similar to firstOrCreate:

/**
     * 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();
        });
    }
Copy the code

The main difference scenario is that firstOrCreate can be used if the search and creation is done under an existing $Attributes. If we need to query the model first and then perform subsequent operations on the model, we should use firstOrNew and save the database operation last. Avoid repeating the save() method.

4. find()

The find() function retrieves data from the primary key, usually a single data, but can also pass in an “array of primary keys” to retrieve multiple models.

$users = User::find([1.2.3]);
Copy the code

Let’s look at its function implementation:

    /**
     * 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 = [The '*'])
    {
        if (is_array($id) || $id instanceof Arrayable) {
            return $this->findMany($id, $columns);
        }

        return $this->whereKey($id)->first($columns);
    }
Copy the code

The first check is whether the id is array, and if so, the findMany function is executed:

    /**
     * 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 = [The '*'])
    {
        if (empty($ids)) {
            return $this->model->newCollection();
        }

        return $this->whereKey($ids)->get($columns);
    }
Copy the code

The result is a Collection type.

conclusion

The Laravel framework has a lot to learn about how Laravel encapsulates methods. There are many ways to see how the source code is implemented, Eloquent ORM, one by one.

More from: laravel-news.com/eloquent-ti…

There are many other functions that can be analyzed, such as:

Relationship with conditions and ordering

public function orders(a) {
    return $this->hasMany('App\Order');    
}
Copy the code

In fact, we can get multiple orders at the same time, add filter statements and sorting. For example, get paid and output in reverse order of update time:

public function paidOrders(a) {
    return $this->hasMany('App\Order')->where('paid'.1)->orderBy('updated_at');
}
Copy the code

“To be continued”