简单解析Lavarel的数据库ORM

前言

在《如何理解laravel服务加载流程》中分析了服务的执行流程,得知框架的所有扩展功能都由服务(Provider)提供。
那么需要先猜想,数据库操作也是通过服务产生的。

分析

查看配置文件有没有服务配置

config/app.php

<?php

return [
	// ...
    'providers' => [
    	// ...
    	// 很明显,确实是通过服务来提供数据库支持的
    	Illuminate\Database\DatabaseServiceProvider::class,
    	// ...
    ];
    // ...
];

查看服务入口

src\Illuminate\Database\DatabaseServiceProvider.php

<?php

namespace Illuminate\Database;

class DatabaseServiceProvider extends ServiceProvider
{
	// ...
    public function boot()
    {
    	// 向模型提供数据库连接解析器
    	// 就是new PDO的入口实例
        Model::setConnectionResolver($this->app['db']);
		// 向模型提供事件调度器
		// 就是用来绑定和触发事件的实例
		// 现在的框架通常都会有一个事件总线
		// 在psr标准中事件总线实例需要绑定在容器中,并按需由容器提供
        Model::setEventDispatcher($this->app['events']);
    }

    // 先注册服务再引导启动服务
    public function register()
    {
    	// 清除初始化状态
    	// 模型初始化放在后面再讲
        Model::clearBootedModels();

		// 注册模型所需的单例
		// 就是一堆$this->app->singleton的操作
        $this->registerConnectionServices();
        // 注册fakephp的
        // 用途不明
        $this->registerEloquentFactory();
        // 用途不明
        $this->registerQueueableEntityResolver();
    }


    protected function registerConnectionServices()
    {
    	// ...
        $this->app->singleton('db', function ($app) {
        	// 连接管理器
            return new DatabaseManager($app, $app['db.factory']);
        });
        // ...
    }
    // ...
}

Eloquent模型

src\Illuminate\Database\Eloquent\Model.php

注:下述没有说明的代码段都来自Model.php

构造函数初始化过程

<?php

namespace Illuminate\Database\Eloquent;
	//.....
abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToString, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
	// 
    public function __construct(array $attributes = [])
    {
    	// 如果没引导启动就马上启动
        $this->bootIfNotBooted();

		// bootIfNotBooted会把需要初始化的内容写入一个静态队列
		// 执行初始化队列
        $this->initializeTraits();

        $this->syncOriginal();

        $this->fill($attributes);
    }

	// ...
	// bootIfNotBooted最后会自行到这里
	// 前面的执行都比较简单,所以可以直接看这个方法
    protected static function bootTraits()
    {
    	// 获取当前类名,如果当前类是子类就返回子类类名
    	// self::class是获取自己的类名
        $class = static::class;

        $booted = [];

        static::$traitInitializers[$class] = [];

    	// 模型初始化过程会向上查找所有use过的trait
        foreach (class_uses_recursive($class) as $trait) {
            $method = 'boot'.class_basename($trait);

    		// 检索到trait中有名为bootTraitName的方法后会马上执行
            if (method_exists($class, $method) && ! in_array($method, $booted)) {
                forward_static_call([$class, $method]);

                $booted[] = $method;
            }
			// 检索到trait中有名为initializeTraitName的方法会将方法名写入初始化队列
			// 后面交给initializeTraits方法执行
            if (method_exists($class, $method = 'initialize'.class_basename($trait))) {
                static::$traitInitializers[$class][] = $method;

                static::$traitInitializers[$class] = array_unique(
                    static::$traitInitializers[$class]
                );
            }
        }
    }
}

Eloquent中如何产生数据库连接

  1. 首先需要在文档中知悉如何使用模型

    摘自learnku.com的一段代码:

    use App\Models\Flight;
    
    foreach (Flight::all() as $flight) {
        echo $flight->name;
    }
  2. 分析all方法

    // 从BaseQuery中执行查询
    // 并返回所有集合
    public static function all($columns = ['*'])
       {
           return static::query()->get(
               is_array($columns) ? $columns : func_get_args()
           );
       }
    
    // 创建一个新的模型实例
    // 并在这个实例中产生新的EloquentBuilder
    public static function query()
       {
           return (new static)->newQuery();
       }
    
    // 获取一个新的Query实例
       public function newQuery()
       {
           return $this->registerGlobalScopes($this->newQueryWithoutScopes());
       }
    
    // 注册全局查询范围
    public function registerGlobalScopes($builder)
       {
           foreach ($this->getGlobalScopes() as $identifier => $scope) {
               $builder->withGlobalScope($identifier, $scope);
           }
    
           return $builder;
       }
    
    // 获取没有查询范围的EloquentQuery实例
    // 这里是为了产生一个全新的实例
       public function newQueryWithoutScopes()
       {
           return $this->newModelQuery()
                       ->with($this->with)
                       ->withCount($this->withCount);
       }
    
    // 获取Eloquent/Builder实例
    // 并绑定当前模型到builder实例
       public function newModelQuery()
       {
           return $this->newEloquentBuilder(
               $this->newBaseQueryBuilder()
           )->setModel($this);
       }
    
    // 产生数据库连接
    // 并获取基础Query实例
       protected function newBaseQueryBuilder()
       {
           return $this->getConnection()->query();
       }
    
    // 获取转述Query实例
       public function newEloquentBuilder($query)
       {
           return new Builder($query);
       }
    
    // 获取从Illuminate\Database\DatabaseManager中获取Illuminate\Database\Connection实例
    public function getConnection()
       {
           return static::resolveConnection($this->getConnectionName());
       }
    
    // 产出连接
    public static function resolveConnection($connection = null)
       {
           return static::$resolver->connection($connection);
       }
    

    $resolver指向哪里:上文中的服务提供者里的Model::setConnectionResolver的入参是DatabaseManager实例,所以$resolver指向的是DatabaseManager

  1. 数据库管理器如何产出连接

    src\Illuminate\Database\DatabaseManager.php

    <?php
    
    namespace Illuminate\Database;
    class DatabaseManager implements ConnectionResolverInterface
    {
    
        public function connection($name = null)
        {
            [$database, $type] = $this->parseConnectionName($name);
    
            $name = $name ?: $database;
    
            if (! isset($this->connections[$name])) {
                $this->connections[$name] = $this->configure(
                    $this->makeConnection($database), $type
                );
            }
    
            return $this->connections[$name];
        }
    
    	protected function makeConnection($name)
        {
            $config = $this->configuration($name);
    
    		// 上下文分析得知extensions这个属性默认都是空的
    		// 所以可以直接调到方法最后
            if (isset($this->extensions[$name])) {
                return call_user_func($this->extensions[$name], $config, $name);
            }
    
            if (isset($this->extensions[$driver = $config['driver']])) {
                return call_user_func($this->extensions[$driver], $config, $name);
            }
    		
    		// 从连接工厂中创建连接
            return $this->factory->make($config, $name);
        }
    
    }

    src\Illuminate\Database\Connectors\ConnectionFactory.php

       public function make(array $config, $name = null)
       {
           $config = $this->parseConfig($config, $name);
    	
    	// 读写分离
           if (isset($config['read'])) {
               return $this->createReadWriteConnection($config);
           }
    	
    	// 默认是执行到这里
    	// 产出一个数据库连接
           return $this->createSingleConnection($config);
       }
    
       protected function createSingleConnection(array $config)
       {
       	// 创建一个pdo实例
           $pdo = $this->createPdoResolver($config);
    		
    	// 创建一个对应的数据库连接
           return $this->createConnection(
               $config['driver'], $pdo, $config['database'], $config['prefix'], $config
           );
       }
    
    // createPdoResolver方法会执行到这里
       protected function createPdoResolverWithoutHosts(array $config)
       {
       	// 这里返回的是一个闭包
       	// 为了在实际执行的时候才会产生连接
           return function () use ($config) {
               return $this->createConnector($config)->connect($config);
           };
       }
    
    // 创建连接器
       public function createConnector(array $config)
       {
           if (! isset($config['driver'])) {
               throw new InvalidArgumentException('A driver must be specified.');
           }
    
           if ($this->container->bound($key = "db.connector.{$config['driver']}")) {
               return $this->container->make($key);
           }
    	// 上述的->connect()就是执行下面这几个实例里的connect方法。
    	// connect()方法会返回一个pdo连接实例,写的都比较简单就不分析了。
           switch ($config['driver']) {
               case 'mysql':
                   return new MySqlConnector;
               case 'pgsql':
                   return new PostgresConnector;
               case 'sqlite':
                   return new SQLiteConnector;
               case 'sqlsrv':
                   return new SqlServerConnector;
           }
    
           throw new InvalidArgumentException("Unsupported driver [{$config['driver']}].");
       }

简单解析Lavarel的数据库ORM
http://blog.icy8.cn/posts/57267/
作者
icy8
发布于
2022年4月20日
许可协议