src/Leevel/Database/Condition.php
protected function addJoin(string $joinType, $names, $cols, $cond = null): self
    {
        // 不能在使用 UNION 查询的同时使用 JOIN 查询
        if (count($this->options['union'])) {
            $e = 'JOIN queries cannot be used while using UNION queries.';

            throw new InvalidArgumentException($e);
        }

        // 是否分析 schema，子表达式不支持
        $parseSchema = true;
        $alias = '';

        if (is_array($names)) {
            $tmp = $names;

            foreach ($tmp as $alias => $names) {
                if (!is_string($alias)) {
                    $e = sprintf('Alias must be string,but %s given.', gettype($alias));

                    throw new InvalidArgumentException($e);
                }

                break;
            }
        }

        if (is_object($names) && ($names instanceof self || $names instanceof Select)) { // 对象子表达式
            $table = $names->makeSql(true);

            if (!$alias) {
                $alias = $names instanceof Select ? $names->databaseCondition()->getAlias() : $names->getAlias();
            }

            $parseSchema = false;
        } elseif (is_object($names) && $names instanceof Closure) { // 回调方法
            $condition = new static($this->connect);
            $condition->setTable($this->getTable());
            call_user_func_array($names, [
                $condition,
            ]);

            $table = $condition->makeSql(true);

            if (!$alias) {
                $alias = $condition->getAlias();
            }

            $parseSchema = false;
        } elseif (is_string($names) && 0 === strpos($names, '(')) { // 字符串子表达式
            if (false !== ($position = strripos($names, 'as'))) {
                $table = trim(substr($names, 0, $position - 1));
                $alias = trim(substr($names, $position + 2));
            } else {
                $table = $names;

                if (!$alias) {
                    $alias = static::DEFAULT_SUBEXPRESSION_ALIAS;
                }
            }

            $parseSchema = false;
        } elseif (is_string($names)) {
            // 字符串指定别名
            if (preg_match('/^(.+)\s+AS\s+(.+)$/i', $names, $matches)) {
                $table = $matches[1];
                $alias = $matches[2];
            } else {
                $table = $names;
            }
        } else {
            throw new InvalidArgumentException('Invalid table name.');
        }

        // 确定 table_name 和 schema
        if (true === $parseSchema) {
            $tmp = explode('.', $table);

            if (isset($tmp[1])) {
                $schema = $tmp[0];
                $tableName = $tmp[1];
            } else {
                $schema = null;
                $tableName = $table;
            }
        } else {
            $schema = null;
            $tableName = $table;
        }

        if (!$alias) {
            $alias = $tableName;
        }

        // 只有表操作才设置当前表
        if ($this->getIsTable()) {
            $this->setTable(
                ($schema ? $schema.'.' : '').$alias
            );

            $this->alias = $alias;
        }

        // 查询条件
        $args = func_get_args();

        if (count($args) > 3) {
            for ($i = 0; $i <= 2; $i++) {
                array_shift($args);
            }

            $select = new static($this->connect);
            $select->setTable($alias);

            call_user_func_array([
                $select,
                'where',
            ], $args);

            $cond = $select->parseWhere(true);
        }

        // 添加一个要查询的数据表
        $this->options['from'][$alias] = [
            'join_type'  => $joinType,
            'table_name' => $tableName,
            'schema'     => $schema,
            'join_cond'  => $cond,
        ];

        // 添加查询字段
        $this->addCols($alias, $cols);

        return $this;
    }
protected function addJoin(string $joinType, $names, $cols, $cond = null): self
    {
        // 不能在使用 UNION 查询的同时使用 JOIN 查询
        if (count($this->options['union'])) {
            $e = 'JOIN queries cannot be used while using UNION queries.';

            throw new InvalidArgumentException($e);
        }

        // 是否分析 schema，子表达式不支持
        $parseSchema = true;
        $alias = '';

        if (is_array($names)) {
            $tmp = $names;

            foreach ($tmp as $alias => $names) {
                if (!is_string($alias)) {
                    $e = sprintf('Alias must be string,but %s given.', gettype($alias));

                    throw new InvalidArgumentException($e);
                }

                break;
            }
        }

        if (is_object($names) && ($names instanceof self || $names instanceof Select)) { // 对象子表达式
            $table = $names->makeSql(true);

            if (!$alias) {
                $alias = $names instanceof Select ? $names->databaseCondition()->getAlias() : $names->getAlias();
            }

            $parseSchema = false;
        } elseif (is_object($names) && $names instanceof Closure) { // 回调方法
            $condition = new static($this->connect);
            $condition->setTable($this->getTable());
            call_user_func_array($names, [
                $condition,
            ]);

            $table = $condition->makeSql(true);

            if (!$alias) {
                $alias = $condition->getAlias();
            }

            $parseSchema = false;
        } elseif (is_string($names) && 0 === strpos($names, '(')) { // 字符串子表达式
            if (false !== ($position = strripos($names, 'as'))) {
                $table = trim(substr($names, 0, $position - 1));
                $alias = trim(substr($names, $position + 2));
            } else {
                $table = $names;

                if (!$alias) {
                    $alias = static::DEFAULT_SUBEXPRESSION_ALIAS;
                }
            }

            $parseSchema = false;
        } elseif (is_string($names)) {
            // 字符串指定别名
            if (preg_match('/^(.+)\s+AS\s+(.+)$/i', $names, $matches)) {
                $table = $matches[1];
                $alias = $matches[2];
            } else {
                $table = $names;
            }
        } else {
            throw new InvalidArgumentException('Invalid table name.');
        }

        // 确定 table_name 和 schema
        if (true === $parseSchema) {
            $tmp = explode('.', $table);

            if (isset($tmp[1])) {
                $schema = $tmp[0];
                $tableName = $tmp[1];
            } else {
                $schema = null;
                $tableName = $table;
            }
        } else {
            $schema = null;
            $tableName = $table;
        }

        if (!$alias) {
            $alias = $tableName;
        }

        // 只有表操作才设置当前表
        if ($this->getIsTable()) {
            $this->setTable(
                ($schema ? $schema.'.' : '').$alias
            );

            $this->alias = $alias;
        }

        // 查询条件
        $args = func_get_args();

        if (count($args) > 3) {
            for ($i = 0; $i <= 2; $i++) {
                array_shift($args);
            }

            $select = new static($this->connect);
            $select->setTable($alias);

            call_user_func_array([
                $select,
                'where',
            ], $args);

            $cond = $select->parseWhere(true);
        }

        // 添加一个要查询的数据表
        $this->options['from'][$alias] = [
            'join_type'  => $joinType,
            'table_name' => $tableName,
            'schema'     => $schema,
            'join_cond'  => $cond,
        ];

        // 添加查询字段
        $this->addCols($alias, $cols);

        return $this;
    }
连表 join 操作.

@param string                                                                   $joinType
@param array|\Closure|\Leevel\Database\Condition|\Leevel\Database\Select|string $names
@param mixed                                                                    $cols
@param mixed                                                                    $cond

@return $this