Laravel - Query(P.1)

Giới thiệu

Xịn chào các bạn, hôm nay mình xin viết bài liên quan đến các query trong laravel. Khi làm việc với Laravel chúng ta hay dùng Eloquent hay Query Builder để thực hiện truy vấn để lấy dữ liệu trong Database, mình luôn thắc mắc rằng khi mình dùng query builder hay eloquent đó thì framework nó sẽ làm gì để sinh ra câu query, và thực thi nó. Nên mình có sử dụng công cụ debug của PHP là Xdebug để debug thử rằng câu query nó thực sự sinh ra bằng cách nào. Vd: Với script này thì nó get tất cả record của table Post với id < 3. app(Post::class)->where('id', '<', 3)->get();

Khi dùng laravel debug mình sẽ thấy được câu query đại loại là vậy SELECT * FROM posts where id < 3;

Vậy câu query nó được sinh ra như thế nào.

Phần 1 mình sẽ nói về query all() dùng để get tất cả các record trong laravel

all();

Mình ví dụ một script như vậy:

Route::get('/posts', function () {
    $posts = app(Post::class)->all();
    dd($posts);
});

Mình bắt đầu debug xem nó hoạt động như thế nào nhé:

  1. Đầu tiên nó sẽ đi vào function load trong class AliasLoader: Mục đích là để load file Model Post của chúng ta. Mình sẽ có bài viết riêng viết về phần load này. Nó nằm ở đây: /vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php
  2. Vì Model post của chúng ta extend Model nên nó sẽ thực thi function khởi tạo 1 instace của Model.
*.../vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php*
 /**
     * Create a new Eloquent model instance.
     *
     * @param  array  $attributes
     * @return void
     */
    public function __construct(array $attributes = [])
    {
        $this->bootIfNotBooted();

        $this->syncOriginal();

        $this->fill($attributes);
    }

Mình sẽ nói về 3 method này ở những bài sau nhé. 3. Vào phần chính là method all() Khi dùng method all() này, debug sẽ chuyển đến method all của Model


    *.../vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php*
    /**
     * Get all of the models from the database.
     *
     * @param  array|mixed  $columns
     * @return \Illuminate\Database\Eloquent\Collection|static[]
     */
    public static function all($columns = ['*'])
    {
        return (new static)->newQuery()->get(
            is_array($columns) ? $columns : func_get_args()
        );
    }

Khi bạn nhìn code này bạn sẽ hiểu là nó new 1 Query với method get nào đó, và truyền vào callback là các column. Tiếp tục nhé: Trong file đó bạn sẽ thấy 1 function newQuery như vậy

/**
     * Get a new query builder for the model's table.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function newQuery()
    {
        $builder = $this->newQueryWithoutScopes();

        foreach ($this->getGlobalScopes() as $identifier => $scope) {
            $builder->withGlobalScope($identifier, $scope);
        }

        return $builder;
    }
    

Khi đó nó sẽ đi tới function newQueryWithoutScopes(), trong function này có đoạn script như sau: $builder = $this->newEloquentBuilder($this->newBaseQueryBuilder());

  • $this->newEloquentBuilder(): Nó sẽ trả về 1 instance của builder gồm những thông số như trong hình này: http://i.prntscr.com/k65F4oiMSuOW03IUZjDj8A.png Những thông số này nhìn vào lúc đầu mình cũng không hiểu gì cả, nhưng về sau mới biết laravel dùng nó để tạo thành câu query bằng việc ghép nối các key và value lại Đây là function của nó
    /**
     * Create a new Eloquent query builder for the model.
     *
     * @param  \Illuminate\Database\Query\Builder  $query
     * @return \Illuminate\Database\Eloquent\Builder|static
     */
    public function newEloquentBuilder($query)
    {
        return new Builder($query);//class Builder bạn có thể xem ở đây: /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php
    }

Ở đây chúng ta có 1 đoạn code nữa:

return $builder->setModel($this)
                    ->with($this->with)
                    ->withCount($this->withCount);

Mình chỉ nói đến phần setModel thôi nhé, phần khác mình bàn sau: function setModel này mục đích là để gán key from bằng table trong database của các bạn. http://i.prntscr.com/2aAz1vZVSIqUDj7yu4CetQ.png Nó lấy table như thế nào: Model Post -> table posts

public function getTable()
  {
      if (! isset($this->table)) {
          return str_replace('\\', '', Str::snake(Str::plural(class_basename($this))));
      }

      return $this->table;
  }

Vậy là chúng ta đã new 1 instance của builder có các tham số cần thiết, vậy new nó để làm gì, mục đích của chúng ta là xem câu query như thế nào mà. Đây nhé: bí kíp võ công nó nằm ở đây: Function get lúc nãy nó nằm ở đây nhé. Nó sẽ đi như thế này:

Từ method: all() trong vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php --> method: get() trong /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php --> method getModel() trong cùng file đó --> và đến function get dưới đây

/**
*/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php*
     * Execute the query as a "select" statement.
     *
     * @param  array  $columns
     * @return \Illuminate\Support\Collection
     */
    public function get($columns = ['*'])
    {
        $original = $this->columns;

        if (is_null($original)) {
            $this->columns = $columns;
        }

        $results = $this->processor->processSelect($this, $this->runSelect());

        $this->columns = $original;

        return collect($results);
    }

Đây là đoạn code mà bạn nên quan tâm: $results = $this->processor->processSelect($this, $this->runSelect()); Tiếp tục nhé: $this->runSelect() --> $this->toSql() --> return $this->grammar->compileSelect($this): ----> method: compileSelect trong vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php --> $sql = parent::compileSelect($query); ---> method: compileSelect trong vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php và cuối cùng là compileComponents() trong file vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php

Mọi việc xử lý nó nằm trong method này nhé:

protected function compileComponents(Builder $query)
   {
       $sql = [];

       foreach ($this->selectComponents as $component) {
           // To compile the query, we'll spin through each component of the query and
           // see if that component exists. If it does we'll just call the compiler
           // function for the component which is responsible for making the SQL.
           if (! is_null($query->$component)) {
               $method = 'compile'.ucfirst($component);

               $sql[$component] = $this->$method($query, $query->$component);
           }
       }

       return $sql;
   }

Ở đây nó sẽ lặp các component trong selectComponents và check nếu khác null trong cái $query builder mà chúng ta thực hiện nãy giờ. http://i.prntscr.com/Y6P-zEahSpKCcbRA1XYrNA.png Các bạn nhìn ở hình này thì thấy rằng cái from là khác null và nó thực hiện để return array sql như sau: http://i.prntscr.com/fYGxs9OjTHGf087jtFUrFw.png Vậy là nó đã làm ra câu query như vậy. Bài viết này cũng khá dài, mình sẽ tiếp tục ở các bài sau nhé.


All Rights Reserved