laravel数据库——入门

本文深入剖析了Laravel框架中数据库操作的流程,从DB facade的使用开始,详细解释了数据库连接的建立、配置、查询的执行,包括连接工厂、连接器、PDO连接及查询构造器的使用。通过分析`DB::select()`的执行过程,展示了从应用容器到数据库查询的完整路径,揭示了Laravel如何优雅地处理数据库交互。
摘要由CSDN通过智能技术生成

laravel数据库——入门


通过laravel数据库源码简单梳理 快速入门 |《Laravel 5.5 中文文档 5.5》| Laravel China 社区文中的知识点。解析最简单laravel数据库查询语句(如下)执行流程。

$results = DB::select('select * from users where id = :id', ['id' => 1]);

准备

DB为应用程序的服务器容器中可用的数据库类提供的一个静态接口。在Facades |《Laravel 5.5 中文文档 5.5》| Laravel China 社区中列出了每个Facade类及其对应的底层类。

Facade服务容器绑定
DBIlluminate\Database\DatabaseManagerdb

因此,我也可以使用app('db')的方式使用DBfacade。
dbIlluminate\Database\DatabaseManager的绑定通过DatabaseServiceProvider来实现。

class DatabaseServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot(){...}

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register(){...}
}

boot方法在服务容器启动时调用。

    public function boot()
    {
        Model::setConnectionResolver($this->app['db']);

        Model::setEventDispatcher($this->app['events']);
    }

boot方法设置类Model的静态属性$resolver$dispatcher
register方法在服务容器启动前调用。

    public function register()
    {
        Model::clearBootedModels();

        $this->registerConnectionServices();

        $this->registerEloquentFactory();

        $this->registerQueueableEntityResolver();
    }

registerConnectionServices方法中注册主要的数据库绑定。db.factoryConnectionFactory进行绑定、dbDatabaseManager进行绑定、db.connection$app['db']->connection()绑定。

    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();
        });
    }

调用

DB类上的调用会经过_callSatatic方法作用在DatabaseManager类对象中。

	// \Illuminate\Support\Facades\Facade::__callStatic
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }
    // \Illuminate\Support\Facades\Facade::getFacadeRoot
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }
    // \Illuminate\Support\Facades\DB::getFacadeAccessor
    protected static function getFacadeAccessor()
    {
        return 'db';
    }

DatabaseManager对象的方法的调用经过_call魔术方法作用在各数据库连接上。

	// \Illuminate\Database\DatabaseManager::__call
    public function __call($method, $parameters)
    {
        return $this->connection()->$method(...$parameters);
    }

connection将检查$connections数组,获取数据库链接。如果没有连接将创建连接。

    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];
    }

链接的创建将由连接工厂类ConnectionFactory实现。

    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);
    }

数据库连接

ConnectionFactorymake方法可以创建Connection类对象。

	// \Illuminate\Database\Connectors\ConnectionFactory::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);
    }

make方法通过配置项区分读写库。

	// \Illuminate\Database\Connectors\ConnectionFactory::createReadWriteConnection
    protected function createReadWriteConnection(array $config)
    {
        $connection = $this->createSingleConnection($this->getWriteConfig($config));

        return $connection->setReadPdo($this->createReadPdo($config));
    }
    // \Illuminate\Database\Connectors\ConnectionFactory::createSingleConnection
    protected function createSingleConnection(array $config)
    {
        $pdo = $this->createPdoResolver($config);

        return $this->createConnection(
            $config['driver'], $pdo, $config['database'], $config['prefix'], $config
        );
    }

createPdoResolver方法使用使用Connector类来配置PDO连接参数。createConnector方法根据配置项创建不同数据库类型的链接器。

	// \Illuminate\Database\Connectors\ConnectionFactory::createPdoResolver
	protected function createPdoResolver(array $config)
    {
        return array_key_exists('host', $config)
                            ? $this->createPdoResolverWithHosts($config)
                            : $this->createPdoResolverWithoutHosts($config);
    }
    // \Illuminate\Database\Connectors\ConnectionFactory::createPdoResolverWithoutHosts
	protected function createPdoResolverWithoutHosts(array $config)
    {
        return function () use ($config) {
            return $this->createConnector($config)->connect($config);
        };
    }
    // \Illuminate\Database\Connectors\ConnectionFactory::createConnector
    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']}]");
    }

链接器的connect方法将根据配置项创建PDO连接。

	// \Illuminate\Database\Connectors\MySqlConnector::connect
	public function connect(array $config)
    {
        $dsn = $this->getDsn($config);

        $options = $this->getOptions($config);

        // We need to grab the PDO options that should be used while making the brand
        // new connection instance. The PDO options control various aspects of the
        // connection's behavior, and some might be specified by the developers.
        $connection = $this->createConnection($dsn, $config, $options);

        if (! empty($config['database'])) {
            $connection->exec("use `{$config['database']}`;");
        }

        $this->configureEncoding($connection, $config);

        // Next, we will check to see if a timezone has been specified in this config
        // and if it has we will issue a statement to modify the timezone with the
        // database. Setting this DB timezone is an optional configuration item.
        $this->configureTimezone($connection, $config);

        $this->setModes($connection, $config);

        return $connection;
    }
    // \Illuminate\Database\Connectors\Connector::createConnection
    public function createConnection($dsn, array $config, array $options)
    {
        [$username, $password] = [
            $config['username'] ?? null, $config['password'] ?? null,
        ];

        try {
            return $this->createPdoConnection(
                $dsn, $username, $password, $options
            );
        } catch (Exception $e) {
            return $this->tryAgainIfCausedByLostConnection(
                $e, $dsn, $username, $password, $options
            );
        }
    }
    // \Illuminate\Database\Connectors\Connector::createPdoConnection
    protected function createPdoConnection($dsn, $username, $password, $options)
    {
        if (class_exists(PDOConnection::class) && ! $this->isPersistentConnection($options)) {
            return new PDOConnection($dsn, $username, $password, $options);
        }

        return new PDO($dsn, $username, $password, $options);
    }

ConnectionFactorycreateConnection方法根据配置项创建不同数据库类型的连接对象。

    \Illuminate\Database\Connectors\ConnectionFactory::createConnection
    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}]");
    }

连接对象内部保存着对PDO连接的引用。

	// \Illuminate\Database\Connection::__construct
	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();
    }

执行SQL

MySqlConnection继承了ConnectionConnection中定义了数据库增删改查的操作方法:
Connection
Connection类属性中保存着pdo的引用,所有的的查询作用于pdo上。

	// \Illuminate\Database\Connection::select
	public function select($query, $bindings = [], $useReadPdo = true)
    {
        return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
            if ($this->pretending()) {
                return [];
            }

            // For select statements, we'll simply execute the query and return an array
            // of the database result set. Each element in the array will be a single
            // row from the database table, and will either be an array or objects.
            $statement = $this->prepared($this->getPdoForSelect($useReadPdo)
                              ->prepare($query));

            $this->bindValues($statement, $this->prepareBindings($bindings));

            $statement->execute();

            return $statement->fetchAll();
        });
    }
    // \Illuminate\Database\Connection::getPdoForSelect
    protected function getPdoForSelect($useReadPdo = true)
    {
        return $useReadPdo ? $this->getReadPdo() : $this->getPdo();
    }

select方法的sql参数经过Grammar编译,与sql进行绑定。

	// \Illuminate\Database\Connection::prepareBindings
	public function prepareBindings(array $bindings)
    {
        $grammar = $this->getQueryGrammar();

        foreach ($bindings as $key => $value) {
            // We need to transform all instances of DateTimeInterface into the actual
            // date string. Each query grammar maintains its own date string format
            // so we'll just ask the grammar for the format to get from the date.
            if ($value instanceof DateTimeInterface) {
                $bindings[$key] = $value->format($grammar->getDateFormat());
            } elseif (is_bool($value)) {
                $bindings[$key] = (int) $value;
            }
        }

        return $bindings;
    }
    // \Illuminate\Database\Connection::bindValues
    public function bindValues($statement, $bindings)
    {
        foreach ($bindings as $key => $value) {
            $statement->bindValue(
                is_string($key) ? $key : $key + 1, $value,
                is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR
            );
        }
    }

Grammar类中定义了把whereselectupdate等方法解析为sql语句的方法。
MySqlGrammar
整体流程
laravel数据库框架结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值