zoukankan      html  css  js  c++  java
  • laravel5.5源码笔记(七、数据库初始化)

    laravel中的数据库也是以服务提供者进行初始化的名为DatabaseServiceProvider,在config文件的providers数组中有写。路径为vendorlaravelframeworksrcIlluminateDatabaseDatabaseServiceProvider.php

    跟以往的serviceProvider一样在register方法中注册,在boot方法中引导加载。

    来看一下register方法。为了保险起见它先通过Model将之前的加载数据给清除掉了。随后开始注册各种数据库连接所用到的对象,通过singleton方法注册一个单例的延迟加载对象到容器中。将DB门面类绑定到了DatabaseManager类中。

        public function register()
        {
            Model::clearBootedModels();
    
            $this->registerConnectionServices();
    
            $this->registerEloquentFactory();
    
            $this->registerQueueableEntityResolver();
        }
    
        protected function registerConnectionServices()
        {
            // The connection factory is used to create the actual connection instances on
            // the database. We will inject the factory into the manager so that it may
            // make the connections while they are actually needed and not of before.
            $this->app->singleton('db.factory', function ($app) {
                return new ConnectionFactory($app);
            });
    
            // The database manager is used to resolve various connections, since multiple
            // connections might be managed. It also implements the connection resolver
            // interface which may be used by other components requiring connections.
            $this->app->singleton('db', function ($app) {
                return new DatabaseManager($app, $app['db.factory']);
            });
    
            $this->app->bind('db.connection', function ($app) {
                return $app['db']->connection();
            });
        }
    
        /**
         * Register the Eloquent factory instance in the container.
         *
         * @return void
         */
        protected function registerEloquentFactory()
        {
            $this->app->singleton(FakerGenerator::class, function ($app) {
                return FakerFactory::create($app['config']->get('app.faker_locale', 'en_US'));
            });
    
            $this->app->singleton(EloquentFactory::class, function ($app) {
                return EloquentFactory::construct(
                    $app->make(FakerGenerator::class), $this->app->databasePath('factories')
                );
            });
        }
    
        /**
         * Register the queueable entity resolver implementation.
         *
         * @return void
         */
        protected function registerQueueableEntityResolver()
        {
            $this->app->singleton(EntityResolver::class, function () {
                return new QueueEntityResolver;
            });
        }

    数据库连接对象ConnectionFactory。这个连接工厂类之中的各种方法都是在创建配置,以及通过配置数组,返回对应的数据库连接实例。这个类中的方法大多是对数据库的连接做一些配置,然后根据这些配置来返回相应的数据库连接实例。

        /**
         * Create a new connection instance.
         *
         * @param  string   $driver
         * @param  PDO|Closure     $connection
         * @param  string   $database
         * @param  string   $prefix
         * @param  array    $config
         * @return IlluminateDatabaseConnection
         *
         * @throws InvalidArgumentException
         */
        protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
        {
            if ($resolver = Connection::getResolver($driver)) {
                return $resolver($connection, $database, $prefix, $config);
            }
    
            switch ($driver) {
                case 'mysql':
                    return new MySqlConnection($connection, $database, $prefix, $config);
                case 'pgsql':
                    return new PostgresConnection($connection, $database, $prefix, $config);
                case 'sqlite':
                    return new SQLiteConnection($connection, $database, $prefix, $config);
                case 'sqlsrv':
                    return new SqlServerConnection($connection, $database, $prefix, $config);
            }
    
            throw new InvalidArgumentException("Unsupported driver [$driver]");
        }

    数据库管理对象DatabaseManager。这个数据库管理类之中的各种方法也是通过各种数据库配置来调用ConnectionFactory工厂来返回数据库连接实例,它会通过配置read,write来返回相应的读写pdo实例。以及包含了数据库实例的创建与断开销毁等。set、get各种配置。如setPdoForType方法来设置数据库连接的读写分离(设置只读、只写)。那么这个类的上游方法在哪里呢。它是从哪里被调用的呢?我们开头提了一句,DB门面类所绑定的类,就是这个类,但是如果我们去这个类中寻找常用的talbe()、query()等方法,肯定是一无所获的,不过我们会发现__call()方法,这个魔术方法会在调用不存在的方法时执行,看一下它的内容。它只有一句代码,从$this->connection()这个对象中,执行相应的方法并返回结果。是的,laravel的源码封装度太高了,这里我们暂时只需要知道$this->connection()代表了数据库连接实例就好。

    public function __call($method, $parameters)
        {
            return $this->connection()->$method(...$parameters);
        }

     

    刚刚说到数据库连接实例,现在我们就来探寻这个实例是如何被创建出来的。如下makeConnection方法所示,通过刚刚说到的ConnectionFactory来返回数据库连接实例。

        /**
         * Prepare the read / write mode for database connection instance.
         *
         * @param  IlluminateDatabaseConnection  $connection
         * @param  string  $type
         * @return IlluminateDatabaseConnection
         */
        protected function setPdoForType(Connection $connection, $type = null)
        {
            if ($type == 'read') {
                $connection->setPdo($connection->getReadPdo());
            } elseif ($type == 'write') {
                $connection->setReadPdo($connection->getPdo());
            }
    
            return $connection;
        }
    
        /**
         * Make the database connection instance.
         *
         * @param  string  $name
         * @return IlluminateDatabaseConnection
         */
        protected function makeConnection($name)
        {
            $config = $this->configuration($name);
    
            // First we will check by the connection name to see if an extension has been
            // registered specifically for that connection. If it has we will call the
            // Closure and pass it the config allowing it to resolve the connection.
            if (isset($this->extensions[$name])) {
                return call_user_func($this->extensions[$name], $config, $name);
            }
    
            // Next we will check to see if an extension has been registered for a driver
            // and will call the Closure if so, which allows us to have a more generic
            // resolver for the drivers themselves which applies to all connections.
            if (isset($this->extensions[$driver = $config['driver']])) {
                return call_user_func($this->extensions[$driver], $config, $name);
            }
    
            return $this->factory->make($config, $name);
        }

    好的,看到DatabaseManager如何创建出数据库连接实例,又要把视线跳到之前说的ConnectionFactory类中了。$this->factory->make($config, $name);最后返回了make方法,我们就从这个方法入手,请看下列代码。

        /**
         * Establish a PDO connection based on the configuration.
         *
         * @param  array   $config
         * @param  string  $name
         * @return IlluminateDatabaseConnection
         */
        public function make(array $config, $name = null)
        {
            //合并配置数组
            $config = $this->parseConfig($config, $name);
    
            if (isset($config['read'])) {
                return $this->createReadWriteConnection($config);
            }
    
            return $this->createSingleConnection($config);
        }
    
        /**
         * Create a single database connection instance.
         *
         * @param  array  $config
         * @return IlluminateDatabaseConnection
         */
        protected function createSingleConnection(array $config)
        {
            $pdo = $this->createPdoResolver($config);
    
            return $this->createConnection(
                $config['driver'], $pdo, $config['database'], $config['prefix'], $config
            );
        }
    
        /**
         * Create a new connection instance.
         *
         * @param  string   $driver
         * @param  PDO|Closure     $connection
         * @param  string   $database
         * @param  string   $prefix
         * @param  array    $config
         * @return IlluminateDatabaseConnection
         *
         * @throws InvalidArgumentException
         */
        protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
        {
            if ($resolver = Connection::getResolver($driver)) {
                return $resolver($connection, $database, $prefix, $config);
            }
    
            switch ($driver) {
                case 'mysql':
                    return new MySqlConnection($connection, $database, $prefix, $config);
                case 'pgsql':
                    return new PostgresConnection($connection, $database, $prefix, $config);
                case 'sqlite':
                    return new SQLiteConnection($connection, $database, $prefix, $config);
                case 'sqlsrv':
                    return new SqlServerConnection($connection, $database, $prefix, $config);
            }
    
            throw new InvalidArgumentException("Unsupported driver [$driver]");
        }

    可以看到,经过一系列的方法跳转,我们终于通过数据库配置,得到了mysql等数据库的连接对象。

    数据库工厂类ConnectionFactory返回的实例连接,我们拿mysql举例。返回的便是MySqlConnection这个类vendorlaravelframeworksrcIlluminateDatabaseMySqlConnection.php

    进入这个类文件,可以看见都是获取grammar相关的方法,这些方法暂时先不去看它。秘密在于它的父类Connection类。(vendorlaravelframeworksrcIlluminateDatabaseConnection.php)先看它的构造方法,从这个方法我们可以知道,所有模型对象也好,DB对象也好,底层都是通过pdo去连接执行的,另外呢,tablePrefix数据表前缀,以及数据库连接配置也是在这里进行加载的。useDefaultQueryGrammar这俩个方法就不去深究了。我们来看点有趣的。

        /**
         * Create a new database connection instance.
         *
         * @param  PDO|Closure     $pdo
         * @param  string   $database
         * @param  string   $tablePrefix
         * @param  array    $config
         * @return void
         */
        public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
        {
            $this->pdo = $pdo;
    
            // First we will setup the default properties. We keep track of the DB
            // name we are connected to since it is needed when some reflective
            // type commands are run such as checking whether a table exists.
            $this->database = $database;
    
            $this->tablePrefix = $tablePrefix;
    
            $this->config = $config;
    
            // We need to initialize a query grammar and the query post processors
            // which are both very important parts of the database abstractions
            // so we initialize these to their default values while starting.
            $this->useDefaultQueryGrammar();
    
            $this->useDefaultPostProcessor();
        }

    有趣的是什么呢?就是这个table()方法了。我们都知道,在laravel中既可以使用模型的方式去进行数据库操作,也可以使用DB::table()的方式,而我们平时使用较多的table()方法的真身,就在这个类里了。这个table方法只做了一件事,就是从$this->query()这个对象中from一个表,然后返回的对象就可以通过链式调用的方式去做其他操作了。接下来看这个$this->query(),这个方法也只做了一件事,new一个QueryBuilder对象。

        /**
         * Begin a fluent query against a database table.
         *
         * @param  string  $table
         * @return IlluminateDatabaseQueryBuilder
         */
        public function table($table)
        {
            return $this->query()->from($table);
        }
    
        /**
         * Get a new query builder instance.
         *
         * @return IlluminateDatabaseQueryBuilder
         */
        public function query()
        {
            return new QueryBuilder(
                $this, $this->getQueryGrammar(), $this->getPostProcessor()
            );
        }

    而QueryBuilder这个对象就是我们平时使用的eloquent orm 的入口,我们平时使用的那些方便的数据库操作方法都是从这里进入。

     通过一系列跳转,我们会发现,这个QueryBuilder的真身在

    vendorlaravelframeworksrcIlluminateDatabaseQueryBuilder.php这个类中,名为Builder类。

    来到这个类文件中,稍微浏览一下,感觉发现了宝藏,里面那些where()、join()、get()、find()方法,不正是我们常用的各种方便的orm方法么?

    来来回回绕了这么大一个圈终于找到,我们常用的函数是从何而来,但是现在还有一个疑问了。现在我们一路跟踪到的线索,都是从DB::table()这种方式跟踪而来,那么model的方式是怎么调用的呢?

    让我们随便新建一个模型类,然后找到它的父类Model

    vendorlaravelframeworksrcIlluminateDatabaseEloquentModel.php

    浏览一番过后,我们发现了比较眼熟的fill()、all()、save()等方法,然后我们会发现这些方法中,大部分都有$this->newModelQuery();这么一句,我们根据这一线索一路跟踪,一路尾行,最终我们会发现new QueryBuilder这句代码又出现了。

        /**
         * Get a new query builder for the model's table.
         *
         * @return IlluminateDatabaseEloquentBuilder
         */
        public function newQuery()
        {
            return $this->registerGlobalScopes($this->newQueryWithoutScopes());
        }
    
        /**
         * Register the global scopes for this builder instance.
         *
         * @param  IlluminateDatabaseEloquentBuilder  $builder
         * @return IlluminateDatabaseEloquentBuilder
         */
        public function registerGlobalScopes($builder)
        {
            foreach ($this->getGlobalScopes() as $identifier => $scope) {
                $builder->withGlobalScope($identifier, $scope);
            }
    
            return $builder;
        }
    
    
        /**
         * Get a new query builder instance for the connection.
         *
         * @return IlluminateDatabaseQueryBuilder
         */
        protected function newBaseQueryBuilder()
        {
            $connection = $this->getConnection();
    
            return new QueryBuilder(
                $connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
            );
        }

    是的,再次跳转后,我们便又回到了vendorlaravelframeworksrcIlluminateDatabaseQueryBuilder.php这个类中。

    至于那些model里没写的方法为什么可以直接调用?你去model类里找一找看有没有__call这个魔术方法,看它里面写了些什么

        /**
         * Handle dynamic method calls into the model.
         *
         * @param  string  $method
         * @param  array  $parameters
         * @return mixed
         */
        public function __call($method, $parameters)
        {
            if (in_array($method, ['increment', 'decrement'])) {
                return $this->$method(...$parameters);
            }
    
            return $this->newQuery()->$method(...$parameters);
        }

    到这里,数据库服务是怎么启动的,DB门面、model类为什么能直接执行orm方法,相信我们已经有清晰的认识了。至于orm是怎么转化成sql语句执行的,且听下回分解~

     

     

     

     

     

  • 相关阅读:
    js 原生ajax实现
    layer 查看图片
    c# 操作XML
    C# 扩展方法
    c# 依赖注入
    visual studio 快捷键
    HIS系统结算后,没有更新单据状态为“已结算”
    网络流四·最小路径覆盖 HihoCoder
    飞行员配对(二分图最大匹配) 51Nod
    开心的小Q 51Nod
  • 原文地址:https://www.cnblogs.com/wyycc/p/10438667.html
Copyright © 2011-2022 走看看