illuminate/database 是完整的php数据库工具包,即ORM(Object-Relational Mapping)类库。

提供丰富的查询构造器,和多个驱动的服务。作为Laravel的数据库层使用,也可以单独使用。

一 使用

加载composer之后,创建Illuminate\Database\Capsule\Manager对象,配置连接数据后可将其全局化,之后调用全局化的变量使用构造方法等进行查询。

二 代码

require_once './vendor/autoload.php';use Illuminate\Container\Container;use Illuminate\Database\Capsule\Manager as DB;use Illuminate\Events\Dispatcher;class BasedDB{private $config;public function __construct($config){$this->config = ['driver' => 'mysql','host' => $config["host"],'database' => $config["database"],'username' => $config["username"],'password' => $config["password"],'charset' => $config["charset"],'collation' => $config["collation"],'prefix' => $config["prefix"],];$this->init();}public function init(){$db = new DB();$db->addConnection($this->config);$db->setEventDispatcher(new Dispatcher(new Container));$db->setAsGlobal(); //设置静态全局可用$db->bootEloquent();}}class TestDB extends BasedDB{public function test1(){//构造查询$info = DB::table('userinfo')->where('id', '=', 2)->get();var_dump($info);}}$config = ['driver' => 'mysql','host' => 'localhost','database' => 'test','username' => 'root','password' => 'qwe110110','charset' => 'utf8','collation' => 'utf8_general_ci','prefix' => '',];$t = new TestDB($config);$t->test1();

三 原理

从代码可见从Illuminate\Database\Capsule\Manager::addConnection($config)开始。

addConnection仅设置配置。该类的构造参数中包含Illuminate\Container\Container类,通过Manager::setupContainer()函数传入Container类对象,获取绑定的配置。

Container类中Container::bound()判断是否有绑定的内容。使用Container::bind()绑定内容,会删除共享实例中对应的别名内容。绑定时若为指定绑定类的具体类或者说闭包,会自动处理,若未执行则实际处理类为指定的类,利用反射返回对应实例,否则直接返回已有实例。

Manager::setEventDispatcher() 从字面意思是设置事件调度程序。传入参数Illuminate\Events\Dispatcher类实例。通过查看代码,Dispatcher可以用Dispatcher::listen()、Dispatcher::push()、Dispatcher::dispatch()、Dispatcher::until()可以设置监听或调度监听。

Manager::setAsGlobal()将Manager全局化,可以用调用静态对象方法调用。

Manager::bootEloquent()启动Eloquent,其中调用Manager::getEventDispatcher()获取默认的程序调度,Eloquent::setEventDispatcher设置Eloquent的事件调度。Manager::getEventDispatcher()获取绑定的events内容。

到此为止初始化结束,没有出现获取pdo的内容。

调用Manager::table()开始查询,通过调用Manager自己的静态类调用connection,即static::$instance->connection()。

之后调用DatabaseManager::connection()方法。

DatabaseManager通过解析传入的配置名获取配置。若配置名为空则默认为default,可以调用DatabaseManager::setDefaultConnection()设置默认配置名。根据配置名获取配置,通过\Illuminate\Database\Connectors\ConnectionFactory工厂类匹配对应驱动,返回对应pdo对象。

比如返回Illuminate\Database\MySqlConnector对象。该类处理对应驱动特殊处理方法,比如数据库连接、设置事务隔离级别等。

table方法最后调用的是Illuminate\Database\Connection::table(),其为MySqlConnector父类。DatabaseManager::__call($method, $parameters)处理传如的数据并执行,该方法实际返回ConnectionFactory类创建的Illuminate\Database\Query\Builder对象,模式构建器调用结束的时候,将构造器对象作为参数,整合成查询字符串执行查询。

四 源码

1、初始化

根据代码可以绑定config类,或者自定义配置;可以设置events。

//Illuminate\Database\Capsule\Manageruse Illuminate\Container\Container;class Manager{use CapsuleManagerTrait;protected $manager;/** * Create a new database capsule manager. * * @param\Illuminate\Container\Container|null$container * @return void */public function __construct(Container $container = null){$this->setupContainer($container ?: new Container);// Once we have the container setup, we will setup the default configuration// options in the container "config" binding. This will make the database// manager work correctly out of the box without extreme configuration.$this->setupDefaultConfiguration();$this->setupManager();} /** * Register a connection with the manager. * * @paramarray$config * @paramstring$name * @return void */public function addConnection(array $config, $name = 'default'){$connections = $this->container['config']['database.connections'];$connections[$name] = $config;$this->container['config']['database.connections'] = $connections;}/** * Get the current event dispatcher instance. * * @return \Illuminate\Contracts\Events\Dispatcher|null */public function getEventDispatcher(){if ($this->container->bound('events')) {return $this->container['events'];}}/** * Setup the default database configuration options. * * @return void */protected function setupDefaultConfiguration(){$this->container['config']['database.fetch'] = PDO::FETCH_OBJ;$this->container['config']['database.default'] = 'default';}/** * Set the event dispatcher instance to be used by connections. * * @param\Illuminate\Contracts\Events\Dispatcher$dispatcher * @return void */public function setEventDispatcher(Dispatcher $dispatcher){$this->container->instance('events', $dispatcher);}/** * Bootstrap Eloquent so it is ready for usage. * * @return void */public function bootEloquent(){Eloquent::setConnectionResolver($this->manager);// If we have an event dispatcher instance, we will go ahead and register it// with the Eloquent ORM, allowing for model callbacks while creating and// updating "model" instances; however, it is not necessary to operate.if ($dispatcher = $this->getEventDispatcher()) {Eloquent::setEventDispatcher($dispatcher);}}}//Illuminate\Support\Traits\CapsuleManagerTraittrait CapsuleManagerTrait{/** * Make this capsule instance available globally. * * @return void */public function setAsGlobal(){static::$instance = $this;} /** * Setup the IoC container instance. * * @param\Illuminate\Contracts\Container\Container$container * @return void */protected function setupContainer(Container $container){$this->container = $container;if (! $this->container->bound('config')) {$this->container->instance('config', new Fluent);}}}//\Illuminate\Contracts\Container\Containeruse Illuminate\Contracts\Container\Container as ContainerContract;class Container implements ArrayAccess, ContainerContract{ /** * Determine if the given abstract type has been bound. * * @paramstring$abstract * @return bool */public function bound($abstract){return isset($this->bindings[$abstract]) || isset($this->instances[$abstract]) || $this->isAlias($abstract);}/** * Register a binding with the container. * * @paramstring$abstract * @param\Closure|string|null$concrete * @parambool$shared * @return void * * @throws \TypeError */public function bind($abstract, $concrete = null, $shared = false){$this->dropStaleInstances($abstract);// If no concrete type was given, we will simply set the concrete type to the// abstract type. After that, the concrete type to be registered as shared// without being forced to state their classes in both of the parameters.if (is_null($concrete)) {$concrete = $abstract;}// If the factory is not a Closure, it means it is just a class name which is// bound into this container to the abstract type and we will just wrap it// up inside its own Closure to give us more convenience when extending.if (! $concrete instanceof Closure) {if (! is_string($concrete)) {throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');}$concrete = $this->getClosure($abstract, $concrete);}$this->bindings[$abstract] = compact('concrete', 'shared');// If the abstract type was already resolved in this container we'll fire the// rebound listener so that any objects which have already gotten resolved// can have their copy of the object updated via the listener callbacks.if ($this->resolved($abstract)) {$this->rebound($abstract);}}/** * Register an existing instance as shared in the container. * * @paramstring$abstract * @parammixed$instance * @return mixed */public function instance($abstract, $instance){$this->removeAbstractAlias($abstract);$isBound = $this->bound($abstract);unset($this->aliases[$abstract]);// We'll check to determine if this type has been bound before, and if it has// we will fire the rebound callbacks registered with the container and it// can be updated with consuming classes that have gotten resolved here.$this->instances[$abstract] = $instance;if ($isBound) {$this->rebound($abstract);}return $instance;}/** * Remove an alias from the contextual binding alias cache. * * @paramstring$searched * @return void */protected function removeAbstractAlias($searched){if (! isset($this->aliases[$searched])) {return;}foreach ($this->abstractAliases as $abstract => $aliases) {foreach ($aliases as $index => $alias) {if ($alias == $searched) {unset($this->abstractAliases[$abstract][$index]);}}}}}

2、构造器

//Illuminate\Database\Capsule\Manageruse Illuminate\Database\DatabaseManager;class Manager{protected $manager;/** * Create a new database capsule manager. * * @param\Illuminate\Container\Container|null$container * @return void */public function __construct(Container $container = null){$this->setupContainer($container ?: new Container);// Once we have the container setup, we will setup the default configuration// options in the container "config" binding. This will make the database// manager work correctly out of the box without extreme configuration.$this->setupDefaultConfiguration();$this->setupManager();} /** * Build the database manager instance. * * @return void */protected function setupManager(){$factory = new ConnectionFactory($this->container);$this->manager = new DatabaseManager($this->container, $factory);}/** * Dynamically pass methods to the default connection. * * @paramstring$method * @paramarray$parameters * @return mixed */public static function __callStatic($method, $parameters){return static::connection()->$method(...$parameters);}/** * Get a connection instance from the global manager. * * @paramstring|null$connection * @return \Illuminate\Database\Connection */public static function connection($connection = null){return static::$instance->getConnection($connection);}/** * Get a registered connection instance. * * @paramstring|null$name * @return \Illuminate\Database\Connection */public function getConnection($name = null){return $this->manager->connection($name);}/** * Get a fluent query builder instance. * * @param\Closure|\Illuminate\Database\Query\Builder|string$table * @paramstring|null$as * @paramstring|null$connection * @return \Illuminate\Database\Query\Builder */public static function table($table, $as = null, $connection = null){return static::$instance->connection($connection)->table($table, $as);}}//Illuminate\Database\DatabaseManageruse Illuminate\Database\Connectors\ConnectionFactory;/** * @mixin \Illuminate\Database\Connection */class DatabaseManager implements ConnectionResolverInterface{public function __construct($app, ConnectionFactory $factory){$this->app = $app;$this->factory = $factory;$this->reconnector = function ($connection) {$this->reconnect($connection->getNameWithReadWriteType());};}/** * Get a database connection instance. * * @paramstring|null$name * @return \Illuminate\Database\Connection */public function connection($name = null){[$database, $type] = $this->parseConnectionName($name);$name = $name ?: $database;// If we haven't created this connection, we'll create it based on the config// provided in the application. Once we've created the connections we will// set the "fetch mode" for PDO which determines the query return types.if (! isset($this->connections[$name])) {$this->connections[$name] = $this->configure($this->makeConnection($database), $type);}return $this->connections[$name];}/** * Make the database connection instance. * * @paramstring$name * @return \Illuminate\Database\Connection */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);}}//Illuminate\Database\Connectors\ConnectionFactoryclass ConnectionFactory{/** * Establish a PDO connection based on the configuration. * * @paramarray$config * @paramstring|null$name * @return \Illuminate\Database\Connection */public function make(array $config, $name = null){$config = $this->parseConfig($config, $name);if (isset($config['read'])) {return $this->createReadWriteConnection($config);}return $this->createSingleConnection($config());}/** * Parse and prepare the database configuration. * * @paramarray$config * @paramstring$name * @return array */protected function parseConfig(array $config, $name){return Arr::add(Arr::add($config, 'prefix', ''), 'name', $name);}/** * Create a read / write database connection instance. * * @paramarray$config * @return \Illuminate\Database\Connection */protected function createReadWriteConnection(array $config){$connection = $this->createSingleConnection($this->getWriteConfig($config));return $connection->setReadPdo($this->createReadPdo($config));}/** * Create a read / write database connection instance. * * @paramarray$config * @return \Illuminate\Database\Connection */protected function createReadWriteConnection(array $config){$connection = $this->createSingleConnection($this->getWriteConfig($config));return $connection->setReadPdo($this->createReadPdo($config));}/** * Create a single database connection instance. * * @paramarray$config * @return \Illuminate\Database\Connection */protected function createSingleConnection(array $config){$pdo = $this->createPdoResolver($config);return $this->createConnection($config['driver'], $pdo, $config['database'], $config['prefix'], $config);}/** * Get the write configuration for a read / write connection. * * @paramarray$config * @return array */protected function getWriteConfig(array $config){return $this->mergeReadWriteConfig($config, $this->getReadWriteConfig($config, 'write'));}/** * Merge a configuration for a read / write connection. * * @paramarray$config * @paramarray$merge * @return array */protected function mergeReadWriteConfig(array $config, array $merge){return Arr::except(array_merge($config, $merge), ['read', 'write']);}/** * Get a read / write level configuration. * * @paramarray$config * @paramstring$type * @return array */protected function getReadWriteConfig(array $config, $type){return isset($config[$type][0])? Arr::random($config[$type]): $config[$type];}/** * Create a single database connection instance. * * @paramarray$config * @return \Illuminate\Database\Connection */protected function createSingleConnection(array $config){$pdo = $this->createPdoResolver($config);return $this->createConnection($config['driver'], $pdo, $config['database'], $config['prefix'], $config);}/** * Create a connector instance based on the configuration. * * @paramarray$config * @return \Illuminate\Database\Connectors\ConnectorInterface * * @throws \InvalidArgumentException */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']}].");}/** * Create a new connection instance. * * @paramstring$driver * @param\PDO|\Closure$connection * @paramstring$database * @paramstring$prefix * @paramarray$config * @return \Illuminate\Database\Connection * * @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}].");}}//Illuminate\Database\MySqlConnectionclass MySqlConnection extends Connection{}//Illuminate\Database\Connectionuse Illuminate\Database\Query\Builder as QueryBuilder;class Connection implements ConnectionInterface{ /** * Begin a fluent query against a database table. * * @param\Closure|\Illuminate\Database\Query\Builder|string$table * @paramstring|null$as * @return \Illuminate\Database\Query\Builder */public function table($table, $as = null){return $this->query()->from($table, $as);}/** * Get a new query builder instance. * * @return \Illuminate\Database\Query\Builder */public function query(){return new QueryBuilder($this, $this->getQueryGrammar(), $this->getPostProcessor());}}//Illuminate\Database\Query\Builderclass Builder{/** * Set the table which the query is targeting. * * @param\Closure|\Illuminate\Database\Query\Builder|string$table * @paramstring|null$as * @return $this */public function from($table, $as = null){if ($this->isQueryable($table)) {return $this->fromSub($table, $as);}$this->from = $as ? "{$table} as {$as}" : $table;return $this;}/** * Determine if the value is a query builder instance or a Closure. * * @parammixed$value * @return bool */protected function isQueryable($value){return $value instanceof self || $value instanceof EloquentBuilder || $value instanceof Relation || $value instanceof Closure;}/** * Makes "from" fetch from a subquery. * * @param\Closure|\Illuminate\Database\Query\Builder|string$query * @paramstring$as * @return $this * * @throws \InvalidArgumentException */public function fromSub($query, $as){[$query, $bindings] = $this->createSub($query);return $this->fromRaw('('.$query.') as '.$this->grammar->wrapTable($as), $bindings);}……//之后都是构建构造器的 没调试 看的也不是太懂}

注:

本地使用php版本7.4,illuminate/database为v8.83.27

illuminate/database composer地址:illuminate/database – Packagist

illuminate/database girbub地址:GitHub – illuminate/database: [READ ONLY] Subtree split of the Illuminate Database component (see laravel/framework)