1<?php 2/** 3 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) 4 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 * 6 * Licensed under The MIT License 7 * For full copyright and license information, please see the LICENSE.txt 8 * Redistributions of files must retain the above copyright notice. 9 * 10 * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 * @link https://cakephp.org CakePHP(tm) Project 12 * @since 3.0.0 13 * @license https://opensource.org/licenses/mit-license.php MIT License 14 */ 15namespace Cake\Database; 16 17use Cake\Database\Expression\FieldInterface; 18use Cake\Database\Expression\IdentifierExpression; 19use Cake\Database\Expression\OrderByExpression; 20 21/** 22 * Contains all the logic related to quoting identifiers in a Query object 23 * 24 * @internal 25 */ 26class IdentifierQuoter 27{ 28 /** 29 * The driver instance used to do the identifier quoting 30 * 31 * @var \Cake\Database\Driver 32 */ 33 protected $_driver; 34 35 /** 36 * Constructor 37 * 38 * @param \Cake\Database\Driver $driver The driver instance used to do the identifier quoting 39 */ 40 public function __construct(Driver $driver) 41 { 42 $this->_driver = $driver; 43 } 44 45 /** 46 * Iterates over each of the clauses in a query looking for identifiers and 47 * quotes them 48 * 49 * @param \Cake\Database\Query $query The query to have its identifiers quoted 50 * @return \Cake\Database\Query 51 */ 52 public function quote(Query $query) 53 { 54 $binder = $query->getValueBinder(); 55 $query->setValueBinder(false); 56 57 if ($query->type() === 'insert') { 58 $this->_quoteInsert($query); 59 } elseif ($query->type() === 'update') { 60 $this->_quoteUpdate($query); 61 } else { 62 $this->_quoteParts($query); 63 } 64 65 $query->traverseExpressions([$this, 'quoteExpression']); 66 $query->setValueBinder($binder); 67 68 return $query; 69 } 70 71 /** 72 * Quotes identifiers inside expression objects 73 * 74 * @param \Cake\Database\ExpressionInterface $expression The expression object to walk and quote. 75 * @return void 76 */ 77 public function quoteExpression($expression) 78 { 79 if ($expression instanceof FieldInterface) { 80 $this->_quoteComparison($expression); 81 82 return; 83 } 84 85 if ($expression instanceof OrderByExpression) { 86 $this->_quoteOrderBy($expression); 87 88 return; 89 } 90 91 if ($expression instanceof IdentifierExpression) { 92 $this->_quoteIdentifierExpression($expression); 93 94 return; 95 } 96 } 97 98 /** 99 * Quotes all identifiers in each of the clauses of a query 100 * 101 * @param \Cake\Database\Query $query The query to quote. 102 * @return void 103 */ 104 protected function _quoteParts($query) 105 { 106 foreach (['distinct', 'select', 'from', 'group'] as $part) { 107 $contents = $query->clause($part); 108 109 if (!is_array($contents)) { 110 continue; 111 } 112 113 $result = $this->_basicQuoter($contents); 114 if (!empty($result)) { 115 $query->{$part}($result, true); 116 } 117 } 118 119 $joins = $query->clause('join'); 120 if ($joins) { 121 $joins = $this->_quoteJoins($joins); 122 $query->join($joins, [], true); 123 } 124 } 125 126 /** 127 * A generic identifier quoting function used for various parts of the query 128 * 129 * @param array $part the part of the query to quote 130 * @return array 131 */ 132 protected function _basicQuoter($part) 133 { 134 $result = []; 135 foreach ((array)$part as $alias => $value) { 136 $value = !is_string($value) ? $value : $this->_driver->quoteIdentifier($value); 137 $alias = is_numeric($alias) ? $alias : $this->_driver->quoteIdentifier($alias); 138 $result[$alias] = $value; 139 } 140 141 return $result; 142 } 143 144 /** 145 * Quotes both the table and alias for an array of joins as stored in a Query 146 * object 147 * 148 * @param array $joins The joins to quote. 149 * @return array 150 */ 151 protected function _quoteJoins($joins) 152 { 153 $result = []; 154 foreach ($joins as $value) { 155 $alias = null; 156 if (!empty($value['alias'])) { 157 $alias = $this->_driver->quoteIdentifier($value['alias']); 158 $value['alias'] = $alias; 159 } 160 161 if (is_string($value['table'])) { 162 $value['table'] = $this->_driver->quoteIdentifier($value['table']); 163 } 164 165 $result[$alias] = $value; 166 } 167 168 return $result; 169 } 170 171 /** 172 * Quotes the table name and columns for an insert query 173 * 174 * @param \Cake\Database\Query $query The insert query to quote. 175 * @return void 176 */ 177 protected function _quoteInsert($query) 178 { 179 list($table, $columns) = $query->clause('insert'); 180 $table = $this->_driver->quoteIdentifier($table); 181 foreach ($columns as &$column) { 182 if (is_scalar($column)) { 183 $column = $this->_driver->quoteIdentifier($column); 184 } 185 } 186 $query->insert($columns)->into($table); 187 } 188 189 /** 190 * Quotes the table name for an update query 191 * 192 * @param \Cake\Database\Query $query The update query to quote. 193 * @return void 194 */ 195 protected function _quoteUpdate($query) 196 { 197 $table = $query->clause('update')[0]; 198 199 if (is_string($table)) { 200 $query->update($this->_driver->quoteIdentifier($table)); 201 } 202 } 203 204 /** 205 * Quotes identifiers in expression objects implementing the field interface 206 * 207 * @param \Cake\Database\Expression\FieldInterface $expression The expression to quote. 208 * @return void 209 */ 210 protected function _quoteComparison(FieldInterface $expression) 211 { 212 $field = $expression->getField(); 213 if (is_string($field)) { 214 $expression->setField($this->_driver->quoteIdentifier($field)); 215 } elseif (is_array($field)) { 216 $quoted = []; 217 foreach ($field as $f) { 218 $quoted[] = $this->_driver->quoteIdentifier($f); 219 } 220 $expression->setField($quoted); 221 } elseif ($field instanceof ExpressionInterface) { 222 $this->quoteExpression($field); 223 } 224 } 225 226 /** 227 * Quotes identifiers in "order by" expression objects 228 * 229 * Strings with spaces are treated as literal expressions 230 * and will not have identifiers quoted. 231 * 232 * @param \Cake\Database\Expression\OrderByExpression $expression The expression to quote. 233 * @return void 234 */ 235 protected function _quoteOrderBy(OrderByExpression $expression) 236 { 237 $expression->iterateParts(function ($part, &$field) { 238 if (is_string($field)) { 239 $field = $this->_driver->quoteIdentifier($field); 240 241 return $part; 242 } 243 if (is_string($part) && strpos($part, ' ') === false) { 244 return $this->_driver->quoteIdentifier($part); 245 } 246 247 return $part; 248 }); 249 } 250 251 /** 252 * Quotes identifiers in "order by" expression objects 253 * 254 * @param \Cake\Database\Expression\IdentifierExpression $expression The identifiers to quote. 255 * @return void 256 */ 257 protected function _quoteIdentifierExpression(IdentifierExpression $expression) 258 { 259 $expression->setIdentifier( 260 $this->_driver->quoteIdentifier($expression->getIdentifier()) 261 ); 262 } 263} 264