摘要:说明本文主要学习模块的源码。这里,就已经得到了链接器实例了,该中还装着一个,下文在其使用时再聊下其具体连接逻辑。
说明:本文主要学习Laravel Database模块的Query Builder源码。实际上,Laravel通过Schema Builder来设计数据库,通过Query Builder来CURD数据库。Query Builder并不复杂或神秘,只是在PDO扩展的基础上又开放封闭的包装了一层,提供了fluent api,使得书写的代码也很简洁流畅。在看下Query Builder源码之前,先大概探索下illuminate/database package的目录结构。
开发环境: Laravel5.3 + PHP7
Folder/File | Description |
---|---|
Capsule | Capsule文件夹下只有一个Manager类,主要实现了容器实例化,DatabaseManager和ConnectionFactory的实例化 |
Connectors | 里面包含了四种DB的链接器:MySQLConnector,PostgresConnector,SQLiteConnector,SqlServerConnector,是主要的组件之一,用来CRUD时链接对应的DB |
Console | 该文件内包含migration和seed的命令,如php artisan db:seed, php artisan migrate |
Eloquent | 该文件夹内包含的就是Eloquent的主要实现类,如重点的Model类,Builder类,Relations子文件夹内包含的表的关系类。是核心的组件,也是类最多的文件夹 |
Events | 装载事件类的文件夹 |
Migrations | 实际执行migrate相关命令的类 |
Query | Query Builder的代码主要在这个文件夹,主要的类是Builder类,还包括Grammars和Processors两大类别,根据四个不同的DB分门别类 |
Schema | 是设计database的主要参与类,主要的类是Builder类和Blueprint类,还有Grammars类别,根据四个不同的DB分门别类 |
Connection class | 数据库链接类,封装了PDO,是重要的类 |
DatabaseManager class | 在DatabaseServiceProvider注册为"db",通常会通过该manager来"向下走"到对应的数据库实现类,是重要的类 |
Seeder class | 主要负责seed命令时的操作 |
Query Builder主要在Query文件夹下,以一行简单又经常使用的代码为例来学习下内部实现的原理吧:
Route::get("/query_builder", function() { // Query Builder return DB::table("users")->where("id", "=", 1)->get(); }); // Illuminate/Support/Facades/DB class DB extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return "db"; } }
在DatabaseServiceProvider已经注册了名为"db"的服务即DatabaseManager对象,则实际上魔术调用DatabaseManager中的table()方法,看下__call()魔术方法源码:
// $method = "table", $parameters = "users" public function __call($method, $parameters) { return $this->connection()->$method(...$parameters); }
所以重点是connection()方法,该方法返回的是Connection对象,看下connection()方法源码:
public function connection($name = null) { // $name = "mysql", $type = null list($name, $type) = $this->parseConnectionName($name); // 首次在$connections[]中没有"mysql" => $mysql_connection,所以需要根据配置创建对应DB连接 if (! isset($this->connections[$name])) { // 重点是makeConnection()创建了mysql连接实例 $connection = $this->makeConnection($name); // 由于$type是null,不是"write"或"read",所以实际上啥也没做 $this->setPdoForType($connection, $type); // 得到连接实例$connection后,还需要对该实例做准备工作,如绑定事件,设置connector $this->connections[$name] = $this->prepare($connection); } return $this->connections[$name]; } protected function parseConnectionName($name) { $name = $name ?: $this->getDefaultConnection(); // 检查是否以::read, ::write结尾 return Str::endsWith($name, ["::read", "::write"]) ? explode("::", $name, 2) : [$name, null]; } public function getDefaultConnection() { // laravel默认是mysql,这里假定是常用的mysql连接 return $this->app["config"]["database.default"]; }
通过上面源码知道重点是makeConnection($name)方法,该方法根据传入的mysql名称,来实例化出一个Connection对象,重点看下makeConnection()源码:
protected function makeConnection($name) { // 从config/database.php中获取"connections.mysql"的配置 $config = $this->getConfig($name); // 如果已经自定义了连接,如在AppServiceProvider的boot()中又使用DatabaseManager::extend()方法自定义了一个"mysql"连接实例, // 那就用该实例,这里假设没有自定义 if (isset($this->extensions[$name])) { return call_user_func($this->extensions[$name], $config, $name); } // $driver = "mysql" $driver = $config["driver"]; if (isset($this->extensions[$driver])) { return call_user_func($this->extensions[$driver], $config, $name); } // 通过ConnectionFactory类工厂模式获取Mysql的连接类 return $this->factory->make($config, $name); }
实际上最后还是通过IlluminateDatabaseConnectorsConnectionFactory来解析出对应的connection,这里使用了工厂模式,看下该工厂类的make()方法源码:
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"] = "mysql", $config["database"] = "homestead"(数据库名称) $config["driver"], $pdo, $config["database"], $config["prefix"], $config ); } protected function createPdoResolver(array $config) { return function () use ($config) { return $this->createConnector($config)->connect($config); }; }
深入代码发现,最后是通过该工厂类的createConnection()方法来造出的一个Connection对象,createConnection()源码就是常见的傻瓜式的工厂构造函数:
protected function createConnection($driver, $connection, $database, $prefix = "", array $config = []) { // 容器中已经绑定了"db.connection.mysql"服务就解析出该服务,这里是没有注册的 if ($this->container->bound($key = "db.connection.{$driver}")) { return $this->container->make($key, [$connection, $database, $prefix, $config]); } // $driver = "mysql" 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]"); }
总之,通过以上一步步分析就拿到了Connection这个对象了,DatabaseManager中的__call()方法中最后执行的是(new MysqlConnection(*))->table("users")->where("id", 1)->get()。
OK, 这里注意下MySqlConnection的构造参数$connection是个闭包,该闭包的值是ConnectionFactory::createPdoResolver()的返回值,看下闭包里的操作:
protected function createPdoResolver(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); } 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"]}]"); }
很简单就能知道该闭包一旦执行时,实际上执行的行为类似于(new MySqlConnector)->connect($config)。
这里,就已经得到了链接器实例MySqlConnection了,该connection中还装着一个(new MySqlConnector)->connect($config),下文在其使用时再聊下其具体连接逻辑。
总结:第一步数据库连接实例化已经走完了,已经拿到了连接实例MySqlConnection,下一步将学习下connect()连接器是如何连接数据库的,和如何编译执行SQL语句得到user_id为1的结果值。到时见。
RightCapital招聘Laravel DevOps
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/21959.html
摘要:,看下源码返回很容易知道返回值是,然后将该值存储在变量中,这时。看下的源码去除掉字符后为返回从源码中可知道返回值为,这时。 说明:本文主要学习下Query Builder编译Fluent Api为SQL的细节和执行SQL的过程。实际上,上一篇聊到了IlluminateDatabaseQueryBuilder这个非常重要的类,这个类含有三个主要的武器:MySqlConnection, M...
说明:本篇主要学习数据库连接阶段和编译SQL语句部分相关源码。实际上,上篇已经聊到Query Builder通过连接工厂类ConnectionFactory构造出了MySqlConnection实例(假设驱动driver是mysql),在该MySqlConnection中主要有三件利器:IlluminateDatabaseMysqlConnector;IlluminateDatabaseQuery...
摘要:根据单一责任开发原则来讲,在的开发过程中每个表都应建立一个对外服务和调用。类似于这样解析的数据操作分两种它们除了有各自的特色外,基本的数据操作都是通过调用方法去完成整个。内并没有太多的代码,大多都是处理数据库链接。 showImg(https://segmentfault.com/img/bVbhjvY?w=600&h=296); 前言 提前预祝猿人们国庆快乐,吃好、喝好、玩好,我会在...
摘要:看下两个方法的源码同样是使用了对象来添加命令和。 说明:本文主要学习Schema Builder和Migration System的使用及相关原理。传统上在设计database时需要写大量的SQL语句,但Laravel提供了Schema Builder这个神器使得在设计database时使用面向对象方法来做,不需要写一行SQL,并且还提供了另一个神器Migration System,可...
摘要:实际上的绑定主要有三种方式且只是一种的,这些已经在学习笔记之实例化源码解析聊过,其实现方法并不复杂。从以上源码发现的反射是个很好用的技术,这里给出个,看下能干些啥打印结果太长了,就不粘贴了。 说明:本文主要学习Laravel中Container的源码,主要学习Container的绑定和解析过程,和解析过程中的依赖解决。分享自己的研究心得,希望对别人有所帮助。实际上Container的绑...
阅读 1060·2021-11-12 10:34
阅读 988·2021-09-30 09:56
阅读 670·2019-08-30 15:54
阅读 2604·2019-08-30 11:14
阅读 1467·2019-08-29 16:44
阅读 3207·2019-08-29 16:35
阅读 2491·2019-08-29 16:22
阅读 2443·2019-08-29 15:39