Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.49% covered (success)
86.49%
32 / 37
83.33% covered (success)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Join
86.49% covered (success)
86.49%
32 / 37
83.33% covered (success)
83.33%
5 / 6
25.42
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 getForeignTable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getColumns
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getJoin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 render
76.19% covered (success)
76.19%
16 / 21
0.00% covered (danger)
0.00%
0 / 1
16.65
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (https://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@noladev.com>
7 * @copyright  Copyright (c) 2009-2025 NOLA Interactive, LLC.
8 * @license    https://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Db\Sql;
15
16use Pop\Db\Sql\Parser\Expression;
17use Pop\Db\Sql\Parser\Operator;
18
19/**
20 * Join class
21 *
22 * @category   Pop
23 * @package    Pop\Db
24 * @author     Nick Sagona, III <dev@noladev.com>
25 * @copyright  Copyright (c) 2009-2025 NOLA Interactive, LLC.
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    6.6.5
28 */
29class Join
30{
31
32    /**
33     * Allowed JOIN keywords
34     * @var array
35     */
36    protected static $allowedJoins = [
37        'JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'FULL JOIN',
38        'OUTER JOIN', 'LEFT OUTER JOIN', 'RIGHT OUTER JOIN', 'FULL OUTER JOIN',
39        'INNER JOIN', 'LEFT INNER JOIN', 'RIGHT INNER JOIN', 'FULL INNER JOIN'
40    ];
41
42    /**
43     * SQL object
44     * @var ?AbstractSql
45     */
46    protected ?AbstractSql $sql = null;
47
48    /**
49     * Foreign table
50     * @var ?string
51     */
52    protected ?string $foreignTable = null;
53
54    /**
55     * Columns
56     * @var array
57     */
58    protected array $columns = [];
59
60    /**
61     * Join type
62     * @var string
63     */
64    protected string $join = 'JOIN';
65
66    /**
67     * Constructor
68     *
69     * Instantiate the JOIN object
70     *
71     * @param  AbstractSql $sql
72     * @param  mixed       $foreignTable
73     * @param  array       $columns
74     * @param  string      $join
75     * @throws Exception
76     */
77    public function __construct(AbstractSql $sql, mixed $foreignTable, array $columns, string $join = 'JOIN')
78    {
79        $this->sql = $sql;
80
81        // If it's a sub-select
82        if (($foreignTable instanceof Select) || ($foreignTable instanceof \Pop\Db\Sql)) {
83            $this->foreignTable = (string)$foreignTable;
84        } else if (is_array($foreignTable)) {
85            if (count($foreignTable) !== 1) {
86                throw new Exception('Error: Only one table can be used in JOIN clause.');
87            }
88            $alias = array_key_first($foreignTable);
89            $table = $foreignTable[$alias];
90            $this->foreignTable = $this->sql->quoteId($table) . ' AS ' . $this->sql->quoteId($alias);
91        } else {
92            $this->foreignTable = $this->sql->quoteId($foreignTable);
93        }
94
95        $this->columns = $columns;
96        $this->join    = (in_array(strtoupper($join), self::$allowedJoins)) ? strtoupper($join) : 'JOIN';
97    }
98
99    /**
100     * Get foreign table
101     *
102     * @return string
103     */
104    public function getForeignTable(): string
105    {
106        return $this->foreignTable;
107    }
108
109    /**
110     * Get columns
111     *
112     * @return array
113     */
114    public function getColumns(): array
115    {
116        return $this->columns;
117    }
118
119    /**
120     * Get JOIN type
121     *
122     * @return string
123     */
124    public function getJoin(): string
125    {
126        return $this->join;
127    }
128
129    /**
130     * Render JOIN
131     *
132     * @return string
133     */
134    public function render(): string
135    {
136        $columns = [];
137
138        foreach ($this->columns as $column1 => $column2) {
139            if (Expression::isShorthand($column1)) {
140                ['column' => $column1, 'operator' => $operator] = Operator::parse($column1);
141                if (($column2 === null) && ($operator == 'NOT')) {
142                    $operator = 'IS ' . $operator;
143                }
144            } else {
145                $operator = ($column2 === null) ? 'IS' : '=';
146            }
147            $operator = ' ' . $operator . ' ';
148
149            if (is_array($column2)) {
150                foreach ($column2 as $c) {
151                    if ($c === null) {
152                        $c = 'NULL';
153                    } else if (is_string($c) && str_contains($c, '.')) {
154                        $c = $this->sql->quoteId($c);
155                    }
156                    $columns[] = ((str_contains($column1, '.')) ? $this->sql->quoteId($column1) : $column1) . $operator . $c;
157                }
158            } else {
159                if ($column2 === null) {
160                    $column2 = 'NULL';
161                } else if (is_string($column2)) {
162                    $column2 = $this->sql->quoteId($column2);
163                }
164                $columns[] = $this->sql->quoteId($column1) . $operator . $column2;
165            }
166        }
167
168        return $this->join . ' ' . $this->foreignTable . ' ON (' . implode(' AND ', $columns) . ')';
169    }
170
171    /**
172     * Return JOIN as string
173     *
174     * @return string
175     */
176    public function __toString(): string
177    {
178        return $this->render();
179    }
180
181}