Command.php 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346
  1. <?php
  2. /**
  3. * @link https://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license https://www.yiiframework.com/license/
  6. */
  7. namespace yii\db;
  8. use Yii;
  9. use yii\base\Component;
  10. use yii\base\NotSupportedException;
  11. /**
  12. * Command represents a SQL statement to be executed against a database.
  13. *
  14. * A command object is usually created by calling [[Connection::createCommand()]].
  15. * The SQL statement it represents can be set via the [[sql]] property.
  16. *
  17. * To execute a non-query SQL (such as INSERT, DELETE, UPDATE), call [[execute()]].
  18. * To execute a SQL statement that returns a result data set (such as SELECT),
  19. * use [[queryAll()]], [[queryOne()]], [[queryColumn()]], [[queryScalar()]], or [[query()]].
  20. *
  21. * For example,
  22. *
  23. * ```php
  24. * $users = $connection->createCommand('SELECT * FROM user')->queryAll();
  25. * ```
  26. *
  27. * Command supports SQL statement preparation and parameter binding.
  28. * Call [[bindValue()]] to bind a value to a SQL parameter;
  29. * Call [[bindParam()]] to bind a PHP variable to a SQL parameter.
  30. * When binding a parameter, the SQL statement is automatically prepared.
  31. * You may also call [[prepare()]] explicitly to prepare a SQL statement.
  32. *
  33. * Command also supports building SQL statements by providing methods such as [[insert()]],
  34. * [[update()]], etc. For example, the following code will create and execute an INSERT SQL statement:
  35. *
  36. * ```php
  37. * $connection->createCommand()->insert('user', [
  38. * 'name' => 'Sam',
  39. * 'age' => 30,
  40. * ])->execute();
  41. * ```
  42. *
  43. * To build SELECT SQL statements, please use [[Query]] instead.
  44. *
  45. * For more details and usage information on Command, see the [guide article on Database Access Objects](guide:db-dao).
  46. *
  47. * @property string $rawSql The raw SQL with parameter values inserted into the corresponding placeholders in
  48. * [[sql]].
  49. * @property string $sql The SQL statement to be executed.
  50. *
  51. * @author Qiang Xue <qiang.xue@gmail.com>
  52. * @since 2.0
  53. */
  54. class Command extends Component
  55. {
  56. /**
  57. * @var Connection the DB connection that this command is associated with
  58. */
  59. public $db;
  60. /**
  61. * @var \PDOStatement the PDOStatement object that this command is associated with
  62. */
  63. public $pdoStatement;
  64. /**
  65. * @var int the default fetch mode for this command.
  66. * @see https://www.php.net/manual/en/pdostatement.setfetchmode.php
  67. */
  68. public $fetchMode = \PDO::FETCH_ASSOC;
  69. /**
  70. * @var array the parameters (name => value) that are bound to the current PDO statement.
  71. * This property is maintained by methods such as [[bindValue()]]. It is mainly provided for logging purpose
  72. * and is used to generate [[rawSql]]. Do not modify it directly.
  73. */
  74. public $params = [];
  75. /**
  76. * @var int the default number of seconds that query results can remain valid in cache.
  77. * Use 0 to indicate that the cached data will never expire. And use a negative number to indicate
  78. * query cache should not be used.
  79. * @see cache()
  80. */
  81. public $queryCacheDuration;
  82. /**
  83. * @var \yii\caching\Dependency the dependency to be associated with the cached query result for this command
  84. * @see cache()
  85. */
  86. public $queryCacheDependency;
  87. /**
  88. * @var array pending parameters to be bound to the current PDO statement.
  89. * @since 2.0.33
  90. */
  91. protected $pendingParams = [];
  92. /**
  93. * @var string the SQL statement that this command represents
  94. */
  95. private $_sql;
  96. /**
  97. * @var string name of the table, which schema, should be refreshed after command execution.
  98. */
  99. private $_refreshTableName;
  100. /**
  101. * @var string|null|false the isolation level to use for this transaction.
  102. * See [[Transaction::begin()]] for details.
  103. */
  104. private $_isolationLevel = false;
  105. /**
  106. * @var callable a callable (e.g. anonymous function) that is called when [[\yii\db\Exception]] is thrown
  107. * when executing the command.
  108. */
  109. private $_retryHandler;
  110. /**
  111. * Enables query cache for this command.
  112. * @param int|null $duration the number of seconds that query result of this command can remain valid in the cache.
  113. * If this is not set, the value of [[Connection::queryCacheDuration]] will be used instead.
  114. * Use 0 to indicate that the cached data will never expire.
  115. * @param \yii\caching\Dependency|null $dependency the cache dependency associated with the cached query result.
  116. * @return $this the command object itself
  117. */
  118. public function cache($duration = null, $dependency = null)
  119. {
  120. $this->queryCacheDuration = $duration === null ? $this->db->queryCacheDuration : $duration;
  121. $this->queryCacheDependency = $dependency;
  122. return $this;
  123. }
  124. /**
  125. * Disables query cache for this command.
  126. * @return $this the command object itself
  127. */
  128. public function noCache()
  129. {
  130. $this->queryCacheDuration = -1;
  131. return $this;
  132. }
  133. /**
  134. * Returns the SQL statement for this command.
  135. * @return string the SQL statement to be executed
  136. */
  137. public function getSql()
  138. {
  139. return $this->_sql;
  140. }
  141. /**
  142. * Specifies the SQL statement to be executed. The SQL statement will be quoted using [[Connection::quoteSql()]].
  143. * The previous SQL (if any) will be discarded, and [[params]] will be cleared as well. See [[reset()]]
  144. * for details.
  145. *
  146. * @param string $sql the SQL statement to be set.
  147. * @return $this this command instance
  148. * @see reset()
  149. * @see cancel()
  150. */
  151. public function setSql($sql)
  152. {
  153. if ($sql !== $this->_sql) {
  154. $this->cancel();
  155. $this->reset();
  156. $this->_sql = $this->db->quoteSql($sql);
  157. }
  158. return $this;
  159. }
  160. /**
  161. * Specifies the SQL statement to be executed. The SQL statement will not be modified in any way.
  162. * The previous SQL (if any) will be discarded, and [[params]] will be cleared as well. See [[reset()]]
  163. * for details.
  164. *
  165. * @param string $sql the SQL statement to be set.
  166. * @return $this this command instance
  167. * @since 2.0.13
  168. * @see reset()
  169. * @see cancel()
  170. */
  171. public function setRawSql($sql)
  172. {
  173. if ($sql !== $this->_sql) {
  174. $this->cancel();
  175. $this->reset();
  176. $this->_sql = $sql;
  177. }
  178. return $this;
  179. }
  180. /**
  181. * Returns the raw SQL by inserting parameter values into the corresponding placeholders in [[sql]].
  182. * Note that the return value of this method should mainly be used for logging purpose.
  183. * It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders.
  184. * @return string the raw SQL with parameter values inserted into the corresponding placeholders in [[sql]].
  185. */
  186. public function getRawSql()
  187. {
  188. if (empty($this->params)) {
  189. return $this->_sql;
  190. }
  191. $params = [];
  192. foreach ($this->params as $name => $value) {
  193. if (is_string($name) && strncmp(':', $name, 1)) {
  194. $name = ':' . $name;
  195. }
  196. if (is_string($value) || $value instanceof Expression) {
  197. $params[$name] = $this->db->quoteValue((string)$value);
  198. } elseif (is_bool($value)) {
  199. $params[$name] = ($value ? 'TRUE' : 'FALSE');
  200. } elseif ($value === null) {
  201. $params[$name] = 'NULL';
  202. } elseif (!is_object($value) && !is_resource($value)) {
  203. $params[$name] = $value;
  204. }
  205. }
  206. if (!isset($params[1])) {
  207. return preg_replace_callback('#(:\w+)#', function ($matches) use ($params) {
  208. $m = $matches[1];
  209. return isset($params[$m]) ? $params[$m] : $m;
  210. }, $this->_sql);
  211. }
  212. $sql = '';
  213. foreach (explode('?', $this->_sql) as $i => $part) {
  214. $sql .= (isset($params[$i]) ? $params[$i] : '') . $part;
  215. }
  216. return $sql;
  217. }
  218. /**
  219. * Prepares the SQL statement to be executed.
  220. * For complex SQL statement that is to be executed multiple times,
  221. * this may improve performance.
  222. * For SQL statement with binding parameters, this method is invoked
  223. * automatically.
  224. * @param bool|null $forRead whether this method is called for a read query. If null, it means
  225. * the SQL statement should be used to determine whether it is for read or write.
  226. * @throws Exception if there is any DB error
  227. */
  228. public function prepare($forRead = null)
  229. {
  230. if ($this->pdoStatement) {
  231. $this->bindPendingParams();
  232. return;
  233. }
  234. $sql = $this->getSql();
  235. if ($sql === '') {
  236. return;
  237. }
  238. if ($this->db->getTransaction()) {
  239. // master is in a transaction. use the same connection.
  240. $forRead = false;
  241. }
  242. if ($forRead || $forRead === null && $this->db->getSchema()->isReadQuery($sql)) {
  243. $pdo = $this->db->getSlavePdo(true);
  244. } else {
  245. $pdo = $this->db->getMasterPdo();
  246. }
  247. try {
  248. $this->pdoStatement = $pdo->prepare($sql);
  249. $this->bindPendingParams();
  250. } catch (\Exception $e) {
  251. $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
  252. $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
  253. throw new Exception($message, $errorInfo, $e->getCode(), $e);
  254. } catch (\Throwable $e) {
  255. $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
  256. throw new Exception($message, null, $e->getCode(), $e);
  257. }
  258. }
  259. /**
  260. * Cancels the execution of the SQL statement.
  261. * This method mainly sets [[pdoStatement]] to be null.
  262. */
  263. public function cancel()
  264. {
  265. $this->pdoStatement = null;
  266. }
  267. /**
  268. * Binds a parameter to the SQL statement to be executed.
  269. * @param string|int $name parameter identifier. For a prepared statement
  270. * using named placeholders, this will be a parameter name of
  271. * the form `:name`. For a prepared statement using question mark
  272. * placeholders, this will be the 1-indexed position of the parameter.
  273. * @param mixed $value the PHP variable to bind to the SQL statement parameter (passed by reference)
  274. * @param int|null $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
  275. * @param int|null $length length of the data type
  276. * @param mixed $driverOptions the driver-specific options
  277. * @return $this the current command being executed
  278. * @see https://www.php.net/manual/en/function.PDOStatement-bindParam.php
  279. */
  280. public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null)
  281. {
  282. $this->prepare();
  283. if ($dataType === null) {
  284. $dataType = $this->db->getSchema()->getPdoType($value);
  285. }
  286. if ($length === null) {
  287. $this->pdoStatement->bindParam($name, $value, $dataType);
  288. } elseif ($driverOptions === null) {
  289. $this->pdoStatement->bindParam($name, $value, $dataType, $length);
  290. } else {
  291. $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions);
  292. }
  293. $this->params[$name] = &$value;
  294. return $this;
  295. }
  296. /**
  297. * Binds pending parameters that were registered via [[bindValue()]] and [[bindValues()]].
  298. * Note that this method requires an active [[pdoStatement]].
  299. */
  300. protected function bindPendingParams()
  301. {
  302. foreach ($this->pendingParams as $name => $value) {
  303. $this->pdoStatement->bindValue($name, $value[0], $value[1]);
  304. }
  305. $this->pendingParams = [];
  306. }
  307. /**
  308. * Binds a value to a parameter.
  309. * @param string|int $name Parameter identifier. For a prepared statement
  310. * using named placeholders, this will be a parameter name of
  311. * the form `:name`. For a prepared statement using question mark
  312. * placeholders, this will be the 1-indexed position of the parameter.
  313. * @param mixed $value The value to bind to the parameter
  314. * @param int|null $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
  315. * @return $this the current command being executed
  316. * @see https://www.php.net/manual/en/function.PDOStatement-bindValue.php
  317. */
  318. public function bindValue($name, $value, $dataType = null)
  319. {
  320. if ($dataType === null) {
  321. $dataType = $this->db->getSchema()->getPdoType($value);
  322. }
  323. $this->pendingParams[$name] = [$value, $dataType];
  324. $this->params[$name] = $value;
  325. return $this;
  326. }
  327. /**
  328. * Binds a list of values to the corresponding parameters.
  329. * This is similar to [[bindValue()]] except that it binds multiple values at a time.
  330. * Note that the SQL data type of each value is determined by its PHP type.
  331. * @param array $values the values to be bound. This must be given in terms of an associative
  332. * array with array keys being the parameter names, and array values the corresponding parameter values,
  333. * e.g. `[':name' => 'John', ':age' => 25]`. By default, the PDO type of each value is determined
  334. * by its PHP type. You may explicitly specify the PDO type by using a [[yii\db\PdoValue]] class: `new PdoValue(value, type)`,
  335. * e.g. `[':name' => 'John', ':profile' => new PdoValue($profile, \PDO::PARAM_LOB)]`.
  336. * @return $this the current command being executed
  337. */
  338. public function bindValues($values)
  339. {
  340. if (empty($values)) {
  341. return $this;
  342. }
  343. $schema = $this->db->getSchema();
  344. foreach ($values as $name => $value) {
  345. if (is_array($value)) { // TODO: Drop in Yii 2.1
  346. $this->pendingParams[$name] = $value;
  347. $this->params[$name] = $value[0];
  348. } elseif ($value instanceof PdoValue) {
  349. $this->pendingParams[$name] = [$value->getValue(), $value->getType()];
  350. $this->params[$name] = $value->getValue();
  351. } else {
  352. if (version_compare(PHP_VERSION, '8.1.0') >= 0) {
  353. if ($value instanceof \BackedEnum) {
  354. $value = $value->value;
  355. } elseif ($value instanceof \UnitEnum) {
  356. $value = $value->name;
  357. }
  358. }
  359. $type = $schema->getPdoType($value);
  360. $this->pendingParams[$name] = [$value, $type];
  361. $this->params[$name] = $value;
  362. }
  363. }
  364. return $this;
  365. }
  366. /**
  367. * Executes the SQL statement and returns query result.
  368. * This method is for executing a SQL query that returns result set, such as `SELECT`.
  369. * @return DataReader the reader object for fetching the query result
  370. * @throws Exception execution failed
  371. */
  372. public function query()
  373. {
  374. return $this->queryInternal('');
  375. }
  376. /**
  377. * Executes the SQL statement and returns ALL rows at once.
  378. * @param int|null $fetchMode the result fetch mode. Please refer to [PHP manual](https://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
  379. * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
  380. * @return array all rows of the query result. Each array element is an array representing a row of data.
  381. * An empty array is returned if the query results in nothing.
  382. * @throws Exception execution failed
  383. */
  384. public function queryAll($fetchMode = null)
  385. {
  386. return $this->queryInternal('fetchAll', $fetchMode);
  387. }
  388. /**
  389. * Executes the SQL statement and returns the first row of the result.
  390. * This method is best used when only the first row of result is needed for a query.
  391. * @param int|null $fetchMode the result fetch mode. Please refer to [PHP manual](https://www.php.net/manual/en/pdostatement.setfetchmode.php)
  392. * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
  393. * @return array|false the first row (in terms of an array) of the query result. False is returned if the query
  394. * results in nothing.
  395. * @throws Exception execution failed
  396. */
  397. public function queryOne($fetchMode = null)
  398. {
  399. return $this->queryInternal('fetch', $fetchMode);
  400. }
  401. /**
  402. * Executes the SQL statement and returns the value of the first column in the first row of data.
  403. * This method is best used when only a single value is needed for a query.
  404. * @return string|int|null|false the value of the first column in the first row of the query result.
  405. * False is returned if there is no value.
  406. * @throws Exception execution failed
  407. */
  408. public function queryScalar()
  409. {
  410. $result = $this->queryInternal('fetchColumn', 0);
  411. if (is_resource($result) && get_resource_type($result) === 'stream') {
  412. return stream_get_contents($result);
  413. }
  414. return $result;
  415. }
  416. /**
  417. * Executes the SQL statement and returns the first column of the result.
  418. * This method is best used when only the first column of result (i.e. the first element in each row)
  419. * is needed for a query.
  420. * @return array the first column of the query result. Empty array is returned if the query results in nothing.
  421. * @throws Exception execution failed
  422. */
  423. public function queryColumn()
  424. {
  425. return $this->queryInternal('fetchAll', \PDO::FETCH_COLUMN);
  426. }
  427. /**
  428. * Creates an INSERT command.
  429. *
  430. * For example,
  431. *
  432. * ```php
  433. * $connection->createCommand()->insert('user', [
  434. * 'name' => 'Sam',
  435. * 'age' => 30,
  436. * ])->execute();
  437. * ```
  438. *
  439. * The method will properly escape the column names, and bind the values to be inserted.
  440. *
  441. * Note that the created command is not executed until [[execute()]] is called.
  442. *
  443. * @param string $table the table that new rows will be inserted into.
  444. * @param array|\yii\db\Query $columns the column data (name => value) to be inserted into the table or instance
  445. * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
  446. * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
  447. * @return $this the command object itself
  448. */
  449. public function insert($table, $columns)
  450. {
  451. $params = [];
  452. $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
  453. return $this->setSql($sql)->bindValues($params);
  454. }
  455. /**
  456. * Creates a batch INSERT command.
  457. *
  458. * For example,
  459. *
  460. * ```php
  461. * $connection->createCommand()->batchInsert('user', ['name', 'age'], [
  462. * ['Tom', 30],
  463. * ['Jane', 20],
  464. * ['Linda', 25],
  465. * ])->execute();
  466. * ```
  467. *
  468. * The method will properly escape the column names, and quote the values to be inserted.
  469. *
  470. * Note that the values in each row must match the corresponding column names.
  471. *
  472. * Also note that the created command is not executed until [[execute()]] is called.
  473. *
  474. * @param string $table the table that new rows will be inserted into.
  475. * @param array $columns the column names
  476. * @param array|\Generator $rows the rows to be batch inserted into the table
  477. * @return $this the command object itself
  478. */
  479. public function batchInsert($table, $columns, $rows)
  480. {
  481. $table = $this->db->quoteSql($table);
  482. $columns = array_map(function ($column) {
  483. return $this->db->quoteSql($column);
  484. }, $columns);
  485. $params = [];
  486. $sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows, $params);
  487. $this->setRawSql($sql);
  488. $this->bindValues($params);
  489. return $this;
  490. }
  491. /**
  492. * Creates a command to insert rows into a database table if
  493. * they do not already exist (matching unique constraints),
  494. * or update them if they do.
  495. *
  496. * For example,
  497. *
  498. * ```php
  499. * $sql = $queryBuilder->upsert('pages', [
  500. * 'name' => 'Front page',
  501. * 'url' => 'https://example.com/', // url is unique
  502. * 'visits' => 0,
  503. * ], [
  504. * 'visits' => new \yii\db\Expression('visits + 1'),
  505. * ], $params);
  506. * ```
  507. *
  508. * The method will properly escape the table and column names.
  509. *
  510. * @param string $table the table that new rows will be inserted into/updated in.
  511. * @param array|Query $insertColumns the column data (name => value) to be inserted into the table or instance
  512. * of [[Query]] to perform `INSERT INTO ... SELECT` SQL statement.
  513. * @param array|bool $updateColumns the column data (name => value) to be updated if they already exist.
  514. * If `true` is passed, the column data will be updated to match the insert column data.
  515. * If `false` is passed, no update will be performed if the column data already exists.
  516. * @param array $params the parameters to be bound to the command.
  517. * @return $this the command object itself.
  518. * @since 2.0.14
  519. */
  520. public function upsert($table, $insertColumns, $updateColumns = true, $params = [])
  521. {
  522. $sql = $this->db->getQueryBuilder()->upsert($table, $insertColumns, $updateColumns, $params);
  523. return $this->setSql($sql)->bindValues($params);
  524. }
  525. /**
  526. * Creates an UPDATE command.
  527. *
  528. * For example,
  529. *
  530. * ```php
  531. * $connection->createCommand()->update('user', ['status' => 1], 'age > 30')->execute();
  532. * ```
  533. *
  534. * or with using parameter binding for the condition:
  535. *
  536. * ```php
  537. * $minAge = 30;
  538. * $connection->createCommand()->update('user', ['status' => 1], 'age > :minAge', [':minAge' => $minAge])->execute();
  539. * ```
  540. *
  541. * The method will properly escape the column names and bind the values to be updated.
  542. *
  543. * Note that the created command is not executed until [[execute()]] is called.
  544. *
  545. * @param string $table the table to be updated.
  546. * @param array $columns the column data (name => value) to be updated.
  547. * @param string|array $condition the condition that will be put in the WHERE part. Please
  548. * refer to [[Query::where()]] on how to specify condition.
  549. * @param array $params the parameters to be bound to the command
  550. * @return $this the command object itself
  551. */
  552. public function update($table, $columns, $condition = '', $params = [])
  553. {
  554. $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params);
  555. return $this->setSql($sql)->bindValues($params);
  556. }
  557. /**
  558. * Creates a DELETE command.
  559. *
  560. * For example,
  561. *
  562. * ```php
  563. * $connection->createCommand()->delete('user', 'status = 0')->execute();
  564. * ```
  565. *
  566. * or with using parameter binding for the condition:
  567. *
  568. * ```php
  569. * $status = 0;
  570. * $connection->createCommand()->delete('user', 'status = :status', [':status' => $status])->execute();
  571. * ```
  572. *
  573. * The method will properly escape the table and column names.
  574. *
  575. * Note that the created command is not executed until [[execute()]] is called.
  576. *
  577. * @param string $table the table where the data will be deleted from.
  578. * @param string|array $condition the condition that will be put in the WHERE part. Please
  579. * refer to [[Query::where()]] on how to specify condition.
  580. * @param array $params the parameters to be bound to the command
  581. * @return $this the command object itself
  582. */
  583. public function delete($table, $condition = '', $params = [])
  584. {
  585. $sql = $this->db->getQueryBuilder()->delete($table, $condition, $params);
  586. return $this->setSql($sql)->bindValues($params);
  587. }
  588. /**
  589. * Creates a SQL command for creating a new DB table.
  590. *
  591. * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
  592. * where name stands for a column name which will be properly quoted by the method, and definition
  593. * stands for the column type which must contain an abstract DB type.
  594. *
  595. * The method [[QueryBuilder::getColumnType()]] will be called
  596. * to convert the abstract column types to physical ones. For example, `string` will be converted
  597. * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
  598. *
  599. * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
  600. * inserted into the generated SQL.
  601. *
  602. * Example usage:
  603. * ```php
  604. * Yii::$app->db->createCommand()->createTable('post', [
  605. * 'id' => 'pk',
  606. * 'title' => 'string',
  607. * 'text' => 'text',
  608. * 'column_name double precision null default null',
  609. * ]);
  610. * ```
  611. *
  612. * @param string $table the name of the table to be created. The name will be properly quoted by the method.
  613. * @param array $columns the columns (name => definition) in the new table.
  614. * @param string|null $options additional SQL fragment that will be appended to the generated SQL.
  615. * @return $this the command object itself
  616. */
  617. public function createTable($table, $columns, $options = null)
  618. {
  619. $sql = $this->db->getQueryBuilder()->createTable($table, $columns, $options);
  620. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  621. }
  622. /**
  623. * Creates a SQL command for renaming a DB table.
  624. * @param string $table the table to be renamed. The name will be properly quoted by the method.
  625. * @param string $newName the new table name. The name will be properly quoted by the method.
  626. * @return $this the command object itself
  627. */
  628. public function renameTable($table, $newName)
  629. {
  630. $sql = $this->db->getQueryBuilder()->renameTable($table, $newName);
  631. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  632. }
  633. /**
  634. * Creates a SQL command for dropping a DB table.
  635. * @param string $table the table to be dropped. The name will be properly quoted by the method.
  636. * @return $this the command object itself
  637. */
  638. public function dropTable($table)
  639. {
  640. $sql = $this->db->getQueryBuilder()->dropTable($table);
  641. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  642. }
  643. /**
  644. * Creates a SQL command for truncating a DB table.
  645. * @param string $table the table to be truncated. The name will be properly quoted by the method.
  646. * @return $this the command object itself
  647. */
  648. public function truncateTable($table)
  649. {
  650. $sql = $this->db->getQueryBuilder()->truncateTable($table);
  651. return $this->setSql($sql);
  652. }
  653. /**
  654. * Creates a SQL command for adding a new DB column.
  655. * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
  656. * @param string $column the name of the new column. The name will be properly quoted by the method.
  657. * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
  658. * to convert the given column type to the physical one. For example, `string` will be converted
  659. * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
  660. * @return $this the command object itself
  661. */
  662. public function addColumn($table, $column, $type)
  663. {
  664. $sql = $this->db->getQueryBuilder()->addColumn($table, $column, $type);
  665. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  666. }
  667. /**
  668. * Creates a SQL command for dropping a DB column.
  669. * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
  670. * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
  671. * @return $this the command object itself
  672. */
  673. public function dropColumn($table, $column)
  674. {
  675. $sql = $this->db->getQueryBuilder()->dropColumn($table, $column);
  676. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  677. }
  678. /**
  679. * Creates a SQL command for renaming a column.
  680. * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
  681. * @param string $oldName the old name of the column. The name will be properly quoted by the method.
  682. * @param string $newName the new name of the column. The name will be properly quoted by the method.
  683. * @return $this the command object itself
  684. */
  685. public function renameColumn($table, $oldName, $newName)
  686. {
  687. $sql = $this->db->getQueryBuilder()->renameColumn($table, $oldName, $newName);
  688. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  689. }
  690. /**
  691. * Creates a SQL command for changing the definition of a column.
  692. * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
  693. * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
  694. * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
  695. * to convert the give column type to the physical one. For example, `string` will be converted
  696. * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
  697. * @return $this the command object itself
  698. */
  699. public function alterColumn($table, $column, $type)
  700. {
  701. $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $type);
  702. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  703. }
  704. /**
  705. * Creates a SQL command for adding a primary key constraint to an existing table.
  706. * The method will properly quote the table and column names.
  707. * @param string $name the name of the primary key constraint.
  708. * @param string $table the table that the primary key constraint will be added to.
  709. * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
  710. * @return $this the command object itself.
  711. */
  712. public function addPrimaryKey($name, $table, $columns)
  713. {
  714. $sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns);
  715. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  716. }
  717. /**
  718. * Creates a SQL command for removing a primary key constraint to an existing table.
  719. * @param string $name the name of the primary key constraint to be removed.
  720. * @param string $table the table that the primary key constraint will be removed from.
  721. * @return $this the command object itself
  722. */
  723. public function dropPrimaryKey($name, $table)
  724. {
  725. $sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table);
  726. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  727. }
  728. /**
  729. * Creates a SQL command for adding a foreign key constraint to an existing table.
  730. * The method will properly quote the table and column names.
  731. * @param string $name the name of the foreign key constraint.
  732. * @param string $table the table that the foreign key constraint will be added to.
  733. * @param string|array $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
  734. * @param string $refTable the table that the foreign key references to.
  735. * @param string|array $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
  736. * @param string|null $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
  737. * @param string|null $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
  738. * @return $this the command object itself
  739. */
  740. public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
  741. {
  742. $sql = $this->db->getQueryBuilder()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update);
  743. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  744. }
  745. /**
  746. * Creates a SQL command for dropping a foreign key constraint.
  747. * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
  748. * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
  749. * @return $this the command object itself
  750. */
  751. public function dropForeignKey($name, $table)
  752. {
  753. $sql = $this->db->getQueryBuilder()->dropForeignKey($name, $table);
  754. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  755. }
  756. /**
  757. * Creates a SQL command for creating a new index.
  758. * @param string $name the name of the index. The name will be properly quoted by the method.
  759. * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
  760. * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
  761. * by commas. The column names will be properly quoted by the method.
  762. * @param bool $unique whether to add UNIQUE constraint on the created index.
  763. * @return $this the command object itself
  764. */
  765. public function createIndex($name, $table, $columns, $unique = false)
  766. {
  767. $sql = $this->db->getQueryBuilder()->createIndex($name, $table, $columns, $unique);
  768. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  769. }
  770. /**
  771. * Creates a SQL command for dropping an index.
  772. * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
  773. * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
  774. * @return $this the command object itself
  775. */
  776. public function dropIndex($name, $table)
  777. {
  778. $sql = $this->db->getQueryBuilder()->dropIndex($name, $table);
  779. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  780. }
  781. /**
  782. * Creates a SQL command for adding an unique constraint to an existing table.
  783. * @param string $name the name of the unique constraint.
  784. * The name will be properly quoted by the method.
  785. * @param string $table the table that the unique constraint will be added to.
  786. * The name will be properly quoted by the method.
  787. * @param string|array $columns the name of the column to that the constraint will be added on.
  788. * If there are multiple columns, separate them with commas.
  789. * The name will be properly quoted by the method.
  790. * @return $this the command object itself.
  791. * @since 2.0.13
  792. */
  793. public function addUnique($name, $table, $columns)
  794. {
  795. $sql = $this->db->getQueryBuilder()->addUnique($name, $table, $columns);
  796. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  797. }
  798. /**
  799. * Creates a SQL command for dropping an unique constraint.
  800. * @param string $name the name of the unique constraint to be dropped.
  801. * The name will be properly quoted by the method.
  802. * @param string $table the table whose unique constraint is to be dropped.
  803. * The name will be properly quoted by the method.
  804. * @return $this the command object itself.
  805. * @since 2.0.13
  806. */
  807. public function dropUnique($name, $table)
  808. {
  809. $sql = $this->db->getQueryBuilder()->dropUnique($name, $table);
  810. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  811. }
  812. /**
  813. * Creates a SQL command for adding a check constraint to an existing table.
  814. * @param string $name the name of the check constraint.
  815. * The name will be properly quoted by the method.
  816. * @param string $table the table that the check constraint will be added to.
  817. * The name will be properly quoted by the method.
  818. * @param string $expression the SQL of the `CHECK` constraint.
  819. * @return $this the command object itself.
  820. * @since 2.0.13
  821. */
  822. public function addCheck($name, $table, $expression)
  823. {
  824. $sql = $this->db->getQueryBuilder()->addCheck($name, $table, $expression);
  825. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  826. }
  827. /**
  828. * Creates a SQL command for dropping a check constraint.
  829. * @param string $name the name of the check constraint to be dropped.
  830. * The name will be properly quoted by the method.
  831. * @param string $table the table whose check constraint is to be dropped.
  832. * The name will be properly quoted by the method.
  833. * @return $this the command object itself.
  834. * @since 2.0.13
  835. */
  836. public function dropCheck($name, $table)
  837. {
  838. $sql = $this->db->getQueryBuilder()->dropCheck($name, $table);
  839. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  840. }
  841. /**
  842. * Creates a SQL command for adding a default value constraint to an existing table.
  843. * @param string $name the name of the default value constraint.
  844. * The name will be properly quoted by the method.
  845. * @param string $table the table that the default value constraint will be added to.
  846. * The name will be properly quoted by the method.
  847. * @param string $column the name of the column to that the constraint will be added on.
  848. * The name will be properly quoted by the method.
  849. * @param mixed $value default value.
  850. * @return $this the command object itself.
  851. * @since 2.0.13
  852. */
  853. public function addDefaultValue($name, $table, $column, $value)
  854. {
  855. $sql = $this->db->getQueryBuilder()->addDefaultValue($name, $table, $column, $value);
  856. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  857. }
  858. /**
  859. * Creates a SQL command for dropping a default value constraint.
  860. * @param string $name the name of the default value constraint to be dropped.
  861. * The name will be properly quoted by the method.
  862. * @param string $table the table whose default value constraint is to be dropped.
  863. * The name will be properly quoted by the method.
  864. * @return $this the command object itself.
  865. * @since 2.0.13
  866. */
  867. public function dropDefaultValue($name, $table)
  868. {
  869. $sql = $this->db->getQueryBuilder()->dropDefaultValue($name, $table);
  870. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  871. }
  872. /**
  873. * Creates a SQL command for resetting the sequence value of a table's primary key.
  874. * The sequence will be reset such that the primary key of the next new row inserted
  875. * will have the specified value or the maximum existing value +1.
  876. * @param string $table the name of the table whose primary key sequence will be reset
  877. * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
  878. * the next new row's primary key will have the maximum existing value +1.
  879. * @return $this the command object itself
  880. * @throws NotSupportedException if this is not supported by the underlying DBMS
  881. */
  882. public function resetSequence($table, $value = null)
  883. {
  884. $sql = $this->db->getQueryBuilder()->resetSequence($table, $value);
  885. return $this->setSql($sql);
  886. }
  887. /**
  888. * Executes a db command resetting the sequence value of a table's primary key.
  889. * Reason for execute is that some databases (Oracle) need several queries to do so.
  890. * The sequence is reset such that the primary key of the next new row inserted
  891. * will have the specified value or the maximum existing value +1.
  892. * @param string $table the name of the table whose primary key sequence is reset
  893. * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
  894. * the next new row's primary key will have the maximum existing value +1.
  895. * @throws NotSupportedException if this is not supported by the underlying DBMS
  896. * @since 2.0.16
  897. */
  898. public function executeResetSequence($table, $value = null)
  899. {
  900. return $this->db->getQueryBuilder()->executeResetSequence($table, $value);
  901. }
  902. /**
  903. * Builds a SQL command for enabling or disabling integrity check.
  904. * @param bool $check whether to turn on or off the integrity check.
  905. * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current
  906. * or default schema.
  907. * @param string $table the table name.
  908. * @return $this the command object itself
  909. * @throws NotSupportedException if this is not supported by the underlying DBMS
  910. */
  911. public function checkIntegrity($check = true, $schema = '', $table = '')
  912. {
  913. $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema, $table);
  914. return $this->setSql($sql);
  915. }
  916. /**
  917. * Builds a SQL command for adding comment to column.
  918. *
  919. * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
  920. * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
  921. * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
  922. * @return $this the command object itself
  923. * @since 2.0.8
  924. */
  925. public function addCommentOnColumn($table, $column, $comment)
  926. {
  927. $sql = $this->db->getQueryBuilder()->addCommentOnColumn($table, $column, $comment);
  928. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  929. }
  930. /**
  931. * Builds a SQL command for adding comment to table.
  932. *
  933. * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
  934. * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
  935. * @return $this the command object itself
  936. * @since 2.0.8
  937. */
  938. public function addCommentOnTable($table, $comment)
  939. {
  940. $sql = $this->db->getQueryBuilder()->addCommentOnTable($table, $comment);
  941. return $this->setSql($sql);
  942. }
  943. /**
  944. * Builds a SQL command for dropping comment from column.
  945. *
  946. * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
  947. * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
  948. * @return $this the command object itself
  949. * @since 2.0.8
  950. */
  951. public function dropCommentFromColumn($table, $column)
  952. {
  953. $sql = $this->db->getQueryBuilder()->dropCommentFromColumn($table, $column);
  954. return $this->setSql($sql)->requireTableSchemaRefresh($table);
  955. }
  956. /**
  957. * Builds a SQL command for dropping comment from table.
  958. *
  959. * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
  960. * @return $this the command object itself
  961. * @since 2.0.8
  962. */
  963. public function dropCommentFromTable($table)
  964. {
  965. $sql = $this->db->getQueryBuilder()->dropCommentFromTable($table);
  966. return $this->setSql($sql);
  967. }
  968. /**
  969. * Creates a SQL View.
  970. *
  971. * @param string $viewName the name of the view to be created.
  972. * @param string|Query $subquery the select statement which defines the view.
  973. * This can be either a string or a [[Query]] object.
  974. * @return $this the command object itself.
  975. * @since 2.0.14
  976. */
  977. public function createView($viewName, $subquery)
  978. {
  979. $sql = $this->db->getQueryBuilder()->createView($viewName, $subquery);
  980. return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
  981. }
  982. /**
  983. * Drops a SQL View.
  984. *
  985. * @param string $viewName the name of the view to be dropped.
  986. * @return $this the command object itself.
  987. * @since 2.0.14
  988. */
  989. public function dropView($viewName)
  990. {
  991. $sql = $this->db->getQueryBuilder()->dropView($viewName);
  992. return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
  993. }
  994. /**
  995. * Executes the SQL statement.
  996. * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
  997. * No result set will be returned.
  998. * @return int number of rows affected by the execution.
  999. * @throws Exception execution failed
  1000. */
  1001. public function execute()
  1002. {
  1003. $sql = $this->getSql();
  1004. list($profile, $rawSql) = $this->logQuery(__METHOD__);
  1005. if ($sql == '') {
  1006. return 0;
  1007. }
  1008. $this->prepare(false);
  1009. try {
  1010. $profile and Yii::beginProfile($rawSql, __METHOD__);
  1011. $this->internalExecute($rawSql);
  1012. $n = $this->pdoStatement->rowCount();
  1013. $profile and Yii::endProfile($rawSql, __METHOD__);
  1014. $this->refreshTableSchema();
  1015. return $n;
  1016. } catch (Exception $e) {
  1017. $profile and Yii::endProfile($rawSql, __METHOD__);
  1018. throw $e;
  1019. }
  1020. }
  1021. /**
  1022. * Logs the current database query if query logging is enabled and returns
  1023. * the profiling token if profiling is enabled.
  1024. * @param string $category the log category.
  1025. * @return array array of two elements, the first is boolean of whether profiling is enabled or not.
  1026. * The second is the rawSql if it has been created.
  1027. */
  1028. protected function logQuery($category)
  1029. {
  1030. if ($this->db->enableLogging) {
  1031. $rawSql = $this->getRawSql();
  1032. Yii::info($rawSql, $category);
  1033. }
  1034. if (!$this->db->enableProfiling) {
  1035. return [false, isset($rawSql) ? $rawSql : null];
  1036. }
  1037. return [true, isset($rawSql) ? $rawSql : $this->getRawSql()];
  1038. }
  1039. /**
  1040. * Performs the actual DB query of a SQL statement.
  1041. * @param string $method method of PDOStatement to be called
  1042. * @param int|null $fetchMode the result fetch mode. Please refer to [PHP manual](https://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
  1043. * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
  1044. * @return mixed the method execution result
  1045. * @throws Exception if the query causes any problem
  1046. * @since 2.0.1 this method is protected (was private before).
  1047. */
  1048. protected function queryInternal($method, $fetchMode = null)
  1049. {
  1050. list($profile, $rawSql) = $this->logQuery('yii\db\Command::query');
  1051. if ($method !== '') {
  1052. $info = $this->db->getQueryCacheInfo($this->queryCacheDuration, $this->queryCacheDependency);
  1053. if (is_array($info)) {
  1054. /* @var $cache \yii\caching\CacheInterface */
  1055. $cache = $info[0];
  1056. $cacheKey = $this->getCacheKey($method, $fetchMode, '');
  1057. $result = $cache->get($cacheKey);
  1058. if (is_array($result) && array_key_exists(0, $result)) {
  1059. Yii::debug('Query result served from cache', 'yii\db\Command::query');
  1060. return $result[0];
  1061. }
  1062. }
  1063. }
  1064. $this->prepare(true);
  1065. try {
  1066. $profile and Yii::beginProfile($rawSql, 'yii\db\Command::query');
  1067. $this->internalExecute($rawSql);
  1068. if ($method === '') {
  1069. $result = new DataReader($this);
  1070. } else {
  1071. if ($fetchMode === null) {
  1072. $fetchMode = $this->fetchMode;
  1073. }
  1074. $result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
  1075. $this->pdoStatement->closeCursor();
  1076. }
  1077. $profile and Yii::endProfile($rawSql, 'yii\db\Command::query');
  1078. } catch (Exception $e) {
  1079. $profile and Yii::endProfile($rawSql, 'yii\db\Command::query');
  1080. throw $e;
  1081. }
  1082. if (isset($cache, $cacheKey, $info)) {
  1083. $cache->set($cacheKey, [$result], $info[1], $info[2]);
  1084. Yii::debug('Saved query result in cache', 'yii\db\Command::query');
  1085. }
  1086. return $result;
  1087. }
  1088. /**
  1089. * Returns the cache key for the query.
  1090. *
  1091. * @param string $method method of PDOStatement to be called
  1092. * @param int $fetchMode the result fetch mode. Please refer to [PHP manual](https://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
  1093. * for valid fetch modes.
  1094. * @return array the cache key
  1095. * @since 2.0.16
  1096. */
  1097. protected function getCacheKey($method, $fetchMode, $rawSql)
  1098. {
  1099. $params = $this->params;
  1100. ksort($params);
  1101. return [
  1102. __CLASS__,
  1103. $method,
  1104. $fetchMode,
  1105. $this->db->dsn,
  1106. $this->db->username,
  1107. $this->getSql(),
  1108. json_encode($params),
  1109. ];
  1110. }
  1111. /**
  1112. * Marks a specified table schema to be refreshed after command execution.
  1113. * @param string $name name of the table, which schema should be refreshed.
  1114. * @return $this this command instance
  1115. * @since 2.0.6
  1116. */
  1117. protected function requireTableSchemaRefresh($name)
  1118. {
  1119. $this->_refreshTableName = $name;
  1120. return $this;
  1121. }
  1122. /**
  1123. * Refreshes table schema, which was marked by [[requireTableSchemaRefresh()]].
  1124. * @since 2.0.6
  1125. */
  1126. protected function refreshTableSchema()
  1127. {
  1128. if ($this->_refreshTableName !== null) {
  1129. $this->db->getSchema()->refreshTableSchema($this->_refreshTableName);
  1130. }
  1131. }
  1132. /**
  1133. * Marks the command to be executed in transaction.
  1134. * @param string|null $isolationLevel The isolation level to use for this transaction.
  1135. * See [[Transaction::begin()]] for details.
  1136. * @return $this this command instance.
  1137. * @since 2.0.14
  1138. */
  1139. protected function requireTransaction($isolationLevel = null)
  1140. {
  1141. $this->_isolationLevel = $isolationLevel;
  1142. return $this;
  1143. }
  1144. /**
  1145. * Sets a callable (e.g. anonymous function) that is called when [[Exception]] is thrown
  1146. * when executing the command. The signature of the callable should be:
  1147. *
  1148. * ```php
  1149. * function (\yii\db\Exception $e, $attempt)
  1150. * {
  1151. * // return true or false (whether to retry the command or rethrow $e)
  1152. * }
  1153. * ```
  1154. *
  1155. * The callable will recieve a database exception thrown and a current attempt
  1156. * (to execute the command) number starting from 1.
  1157. *
  1158. * @param callable $handler a PHP callback to handle database exceptions.
  1159. * @return $this this command instance.
  1160. * @since 2.0.14
  1161. */
  1162. protected function setRetryHandler(callable $handler)
  1163. {
  1164. $this->_retryHandler = $handler;
  1165. return $this;
  1166. }
  1167. /**
  1168. * Executes a prepared statement.
  1169. *
  1170. * It's a wrapper around [[\PDOStatement::execute()]] to support transactions
  1171. * and retry handlers.
  1172. *
  1173. * @param string|null $rawSql the rawSql if it has been created.
  1174. * @throws Exception if execution failed.
  1175. * @since 2.0.14
  1176. */
  1177. protected function internalExecute($rawSql)
  1178. {
  1179. $attempt = 0;
  1180. while (true) {
  1181. try {
  1182. if (
  1183. ++$attempt === 1
  1184. && $this->_isolationLevel !== false
  1185. && $this->db->getTransaction() === null
  1186. ) {
  1187. $this->db->transaction(function () use ($rawSql) {
  1188. $this->internalExecute($rawSql);
  1189. }, $this->_isolationLevel);
  1190. } else {
  1191. $this->pdoStatement->execute();
  1192. }
  1193. break;
  1194. } catch (\Exception $e) {
  1195. $rawSql = $rawSql ?: $this->getRawSql();
  1196. $e = $this->db->getSchema()->convertException($e, $rawSql);
  1197. if ($this->_retryHandler === null || !call_user_func($this->_retryHandler, $e, $attempt)) {
  1198. throw $e;
  1199. }
  1200. }
  1201. }
  1202. }
  1203. /**
  1204. * Resets command properties to their initial state.
  1205. *
  1206. * @since 2.0.13
  1207. */
  1208. protected function reset()
  1209. {
  1210. $this->_sql = null;
  1211. $this->pendingParams = [];
  1212. $this->params = [];
  1213. $this->_refreshTableName = null;
  1214. $this->_isolationLevel = false;
  1215. }
  1216. }