123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- <?php
- /**
- * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- * @link https://cakephp.org CakePHP(tm) Project
- * @since 3.0.0
- * @license https://opensource.org/licenses/mit-license.php MIT License
- */
- namespace Cake\Database\Expression;
- use Cake\Database\Exception;
- use Cake\Database\ExpressionInterface;
- use Cake\Database\Query;
- use Cake\Database\TypeMapTrait;
- use Cake\Database\Type\ExpressionTypeCasterTrait;
- use Cake\Database\ValueBinder;
- /**
- * An expression object to contain values being inserted.
- *
- * Helps generate SQL with the correct number of placeholders and bind
- * values correctly into the statement.
- */
- class ValuesExpression implements ExpressionInterface
- {
- use ExpressionTypeCasterTrait;
- use TypeMapTrait;
- /**
- * Array of values to insert.
- *
- * @var array
- */
- protected $_values = [];
- /**
- * List of columns to ensure are part of the insert.
- *
- * @var array
- */
- protected $_columns = [];
- /**
- * The Query object to use as a values expression
- *
- * @var \Cake\Database\Query|null
- */
- protected $_query;
- /**
- * Whether or not values have been casted to expressions
- * already.
- *
- * @var bool
- */
- protected $_castedExpressions = false;
- /**
- * Constructor
- *
- * @param array $columns The list of columns that are going to be part of the values.
- * @param \Cake\Database\TypeMap $typeMap A dictionary of column -> type names
- */
- public function __construct(array $columns, $typeMap)
- {
- $this->_columns = $columns;
- $this->setTypeMap($typeMap);
- }
- /**
- * Add a row of data to be inserted.
- *
- * @param array|\Cake\Database\Query $data Array of data to append into the insert, or
- * a query for doing INSERT INTO .. SELECT style commands
- * @return void
- * @throws \Cake\Database\Exception When mixing array + Query data types.
- */
- public function add($data)
- {
- if ((count($this->_values) && $data instanceof Query) ||
- ($this->_query && is_array($data))
- ) {
- throw new Exception(
- 'You cannot mix subqueries and array data in inserts.'
- );
- }
- if ($data instanceof Query) {
- $this->setQuery($data);
- return;
- }
- $this->_values[] = $data;
- $this->_castedExpressions = false;
- }
- /**
- * Sets the columns to be inserted.
- *
- * @param array $cols Array with columns to be inserted.
- * @return $this
- */
- public function setColumns($cols)
- {
- $this->_columns = $cols;
- $this->_castedExpressions = false;
- return $this;
- }
- /**
- * Gets the columns to be inserted.
- *
- * @return array
- */
- public function getColumns()
- {
- return $this->_columns;
- }
- /**
- * Sets the columns to be inserted. If no params are passed, then it returns
- * the currently stored columns.
- *
- * @deprecated 3.4.0 Use setColumns()/getColumns() instead.
- * @param array|null $cols Array with columns to be inserted.
- * @return array|$this
- */
- public function columns($cols = null)
- {
- deprecationWarning(
- 'ValuesExpression::columns() is deprecated. ' .
- 'Use ValuesExpression::setColumns()/getColumns() instead.'
- );
- if ($cols !== null) {
- return $this->setColumns($cols);
- }
- return $this->getColumns();
- }
- /**
- * Get the bare column names.
- *
- * Because column names could be identifier quoted, we
- * need to strip the identifiers off of the columns.
- *
- * @return array
- */
- protected function _columnNames()
- {
- $columns = [];
- foreach ($this->_columns as $col) {
- if (is_string($col)) {
- $col = trim($col, '`[]"');
- }
- $columns[] = $col;
- }
- return $columns;
- }
- /**
- * Sets the values to be inserted.
- *
- * @param array $values Array with values to be inserted.
- * @return $this
- */
- public function setValues($values)
- {
- $this->_values = $values;
- $this->_castedExpressions = false;
- return $this;
- }
- /**
- * Gets the values to be inserted.
- *
- * @return array
- */
- public function getValues()
- {
- if (!$this->_castedExpressions) {
- $this->_processExpressions();
- }
- return $this->_values;
- }
- /**
- * Sets the values to be inserted. If no params are passed, then it returns
- * the currently stored values
- *
- * @deprecated 3.4.0 Use setValues()/getValues() instead.
- * @param array|null $values Array with values to be inserted.
- * @return array|$this
- */
- public function values($values = null)
- {
- deprecationWarning(
- 'ValuesExpression::values() is deprecated. ' .
- 'Use ValuesExpression::setValues()/getValues() instead.'
- );
- if ($values !== null) {
- return $this->setValues($values);
- }
- return $this->getValues();
- }
- /**
- * Sets the query object to be used as the values expression to be evaluated
- * to insert records in the table.
- *
- * @param \Cake\Database\Query $query The query to set
- * @return $this
- */
- public function setQuery(Query $query)
- {
- $this->_query = $query;
- return $this;
- }
- /**
- * Gets the query object to be used as the values expression to be evaluated
- * to insert records in the table.
- *
- * @return \Cake\Database\Query|null
- */
- public function getQuery()
- {
- return $this->_query;
- }
- /**
- * Sets the query object to be used as the values expression to be evaluated
- * to insert records in the table. If no params are passed, then it returns
- * the currently stored query
- *
- * @deprecated 3.4.0 Use setQuery()/getQuery() instead.
- * @param \Cake\Database\Query|null $query The query to set
- * @return \Cake\Database\Query|null|$this
- */
- public function query(Query $query = null)
- {
- deprecationWarning(
- 'ValuesExpression::query() is deprecated. ' .
- 'Use ValuesExpression::setQuery()/getQuery() instead.'
- );
- if ($query !== null) {
- return $this->setQuery($query);
- }
- return $this->getQuery();
- }
- /**
- * Convert the values into a SQL string with placeholders.
- *
- * @param \Cake\Database\ValueBinder $generator Placeholder generator object
- * @return string
- */
- public function sql(ValueBinder $generator)
- {
- if (empty($this->_values) && empty($this->_query)) {
- return '';
- }
- if (!$this->_castedExpressions) {
- $this->_processExpressions();
- }
- $columns = $this->_columnNames();
- $defaults = array_fill_keys($columns, null);
- $placeholders = [];
- $types = [];
- $typeMap = $this->getTypeMap();
- foreach ($defaults as $col => $v) {
- $types[$col] = $typeMap->type($col);
- }
- foreach ($this->_values as $row) {
- $row += $defaults;
- $rowPlaceholders = [];
- foreach ($columns as $column) {
- $value = $row[$column];
- if ($value instanceof ExpressionInterface) {
- $rowPlaceholders[] = '(' . $value->sql($generator) . ')';
- continue;
- }
- $placeholder = $generator->placeholder('c');
- $rowPlaceholders[] = $placeholder;
- $generator->bind($placeholder, $value, $types[$column]);
- }
- $placeholders[] = implode(', ', $rowPlaceholders);
- }
- if ($this->getQuery()) {
- return ' ' . $this->getQuery()->sql($generator);
- }
- return sprintf(' VALUES (%s)', implode('), (', $placeholders));
- }
- /**
- * Traverse the values expression.
- *
- * This method will also traverse any queries that are to be used in the INSERT
- * values.
- *
- * @param callable $visitor The visitor to traverse the expression with.
- * @return void
- */
- public function traverse(callable $visitor)
- {
- if ($this->_query) {
- return;
- }
- if (!$this->_castedExpressions) {
- $this->_processExpressions();
- }
- foreach ($this->_values as $v) {
- if ($v instanceof ExpressionInterface) {
- $v->traverse($visitor);
- }
- if (!is_array($v)) {
- continue;
- }
- foreach ($v as $column => $field) {
- if ($field instanceof ExpressionInterface) {
- $visitor($field);
- $field->traverse($visitor);
- }
- }
- }
- }
- /**
- * Converts values that need to be casted to expressions
- *
- * @return void
- */
- protected function _processExpressions()
- {
- $types = [];
- $typeMap = $this->getTypeMap();
- $columns = $this->_columnNames();
- foreach ($columns as $c) {
- if (!is_scalar($c)) {
- continue;
- }
- $types[$c] = $typeMap->type($c);
- }
- $types = $this->_requiresToExpressionCasting($types);
- if (empty($types)) {
- return;
- }
- foreach ($this->_values as $row => $values) {
- foreach ($types as $col => $type) {
- /* @var \Cake\Database\Type\ExpressionTypeInterface $type */
- $this->_values[$row][$col] = $type->toExpression($values[$col]);
- }
- }
- $this->_castedExpressions = true;
- }
- }
|