vendor/pimcore/pimcore/lib/Db/PimcoreExtensionsTrait.php line 254

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Db;
  15. use Doctrine\DBAL\Cache\CacheException;
  16. use Doctrine\DBAL\Cache\QueryCacheProfile;
  17. use Doctrine\DBAL\Driver\Exception as DriverException;
  18. use Doctrine\DBAL\Driver\Result;
  19. use Doctrine\DBAL\Driver\ResultStatement;
  20. use Doctrine\DBAL\Exception as DBALException;
  21. use Pimcore\Model\Element\ValidationException;
  22. /**
  23.  * @property \Doctrine\DBAL\Driver\Connection $_conn
  24.  */
  25. trait PimcoreExtensionsTrait
  26. {
  27.     /**
  28.      * Specifies whether the connection automatically quotes identifiers.
  29.      * If true, the methods insert(), update() apply identifier quoting automatically.
  30.      * If false, developer must quote identifiers themselves by calling quoteIdentifier().
  31.      *
  32.      * @var bool
  33.      */
  34.     protected $autoQuoteIdentifiers true;
  35.     /**
  36.      * @see \Doctrine\DBAL\Connection::connect
  37.      */
  38.     public function connect()
  39.     {
  40.         $returnValue parent::connect();
  41.         if ($returnValue) {
  42.             $this->_conn->query('SET default_storage_engine=InnoDB;');
  43.             $this->_conn->query("SET sql_mode = '';");
  44.         }
  45.         return $returnValue;
  46.     }
  47.     /**
  48.      * @see \Doctrine\DBAL\Connection::executeQuery
  49.      *
  50.      * @return ResultStatement
  51.      *
  52.      * @throws DBALException
  53.      */
  54.     public function query(...$params)
  55.     {
  56.         // compatibility layer for additional parameters in the 2nd argument
  57.         // eg. $db->query("UPDATE myTest SET date = ? WHERE uri = ?", [time(), $uri]);
  58.         if (func_num_args() === 2) {
  59.             if (is_array($params[1])) {
  60.                 return parent::executeQuery($params[0], $params[1]);
  61.             }
  62.         }
  63.         if (count($params) > 0) {
  64.             $params[0] = $this->normalizeQuery($params[0], [], true);
  65.         }
  66.         return parent::executeQuery(...$params);
  67.     }
  68.     /**
  69.      * @see \Doctrine\DBAL\Connection::executeQuery
  70.      *
  71.      * @param string $query The SQL query to execute.
  72.      * @param array $params The parameters to bind to the query, if any.
  73.      * @param array $types The types the previous parameters are in.
  74.      * @param QueryCacheProfile|null $qcp The query cache profile, optional.
  75.      *
  76.      * @return ResultStatement The executed statement.
  77.      *
  78.      * @throws DBALException
  79.      */
  80.     public function executeQuery($query, array $params = [], $types = [], QueryCacheProfile $qcp null)
  81.     {
  82.         list($query$params) = $this->normalizeQuery($query$params);
  83.         return parent::executeQuery($query$params$types$qcp);
  84.     }
  85.     /**
  86.      * @see \Doctrine\DBAL\Connection::executeUpdate
  87.      *
  88.      * @param string $query The SQL query.
  89.      * @param array $params The query parameters.
  90.      * @param array $types The parameter types.
  91.      *
  92.      * @return int The number of affected rows.
  93.      *
  94.      * @throws DBALException
  95.      */
  96.     public function executeUpdate($query, array $params = [], array $types = [])
  97.     {
  98.         list($query$params) = $this->normalizeQuery($query$params);
  99.         return parent::executeStatement($query$params$types);
  100.     }
  101.     /**
  102.      * @see \Doctrine\DBAL\Connection::executeCacheQuery
  103.      *
  104.      * @param string $query The SQL query to execute.
  105.      * @param array $params The parameters to bind to the query, if any.
  106.      * @param array $types The types the previous parameters are in.
  107.      * @param QueryCacheProfile $qcp The query cache profile.
  108.      *
  109.      * @return ResultStatement
  110.      *
  111.      * @throws CacheException
  112.      */
  113.     public function executeCacheQuery($query$params$typesQueryCacheProfile $qcp)
  114.     {
  115.         list($query$params) = $this->normalizeQuery($query$params);
  116.         return parent::executeCacheQuery($query$params$types$qcp);
  117.     }
  118.     /**
  119.      * @param string $query
  120.      * @param array $params
  121.      * @param bool $onlyQuery
  122.      *
  123.      * @return array|string
  124.      */
  125.     private function normalizeQuery($query, array $params = [], $onlyQuery false)
  126.     {
  127.         if ($onlyQuery) {
  128.             return $query;
  129.         }
  130.         return [$query$params];
  131.     }
  132.     /**
  133.      * @see \Doctrine\DBAL\Connection::update
  134.      *
  135.      * @param string $tableExpression  The expression of the table to update quoted or unquoted.
  136.      * @param array  $data       An associative array containing column-value pairs.
  137.      * @param array  $identifier The update criteria. An associative array containing column-value pairs.
  138.      * @param array  $types      Types of the merged $data and $identifier arrays in that order.
  139.      *
  140.      * @return int The number of affected rows.
  141.      *
  142.      * @throws DBALException
  143.      */
  144.     public function update($tableExpression, array $data, array $identifier, array $types = [])
  145.     {
  146.         $data $this->quoteDataIdentifiers($data);
  147.         $identifier $this->quoteDataIdentifiers($identifier);
  148.         return parent::update($tableExpression$data$identifier$types);
  149.     }
  150.     /**
  151.      * @see \Doctrine\DBAL\Connection::insert
  152.      *
  153.      * @param string $tableExpression The expression of the table to insert data into, quoted or unquoted.
  154.      * @param array  $data      An associative array containing column-value pairs.
  155.      * @param array  $types     Types of the inserted data.
  156.      *
  157.      * @return int The number of affected rows.
  158.      *
  159.      * @throws DBALException
  160.      */
  161.     public function insert($tableExpression, array $data, array $types = [])
  162.     {
  163.         $data $this->quoteDataIdentifiers($data);
  164.         return parent::insert($tableExpression$data$types);
  165.     }
  166.     /**
  167.      * Deletes table rows based on a custom WHERE clause.
  168.      *
  169.      * @param  mixed        $table The table to update.
  170.      * @param  mixed        $where DELETE WHERE clause(s).
  171.      *
  172.      * @return int          The number of affected rows.
  173.      *
  174.      * @throws DBALException
  175.      */
  176.     public function deleteWhere($table$where '')
  177.     {
  178.         $sql 'DELETE FROM ' $table;
  179.         if ($where) {
  180.             $sql .= ' WHERE ' $where;
  181.         }
  182.         return $this->executeUpdate($sql);
  183.     }
  184.     /**
  185.      * Updates table rows with specified data based on a custom WHERE clause.
  186.      *
  187.      * @param  mixed        $table The table to update.
  188.      * @param  array        $data  Column-value pairs.
  189.      * @param  mixed        $where UPDATE WHERE clause(s).
  190.      *
  191.      * @return int          The number of affected rows.
  192.      *
  193.      * @throws DBALException
  194.      */
  195.     public function updateWhere($table, array $data$where '')
  196.     {
  197.         $set = [];
  198.         $paramValues = [];
  199.         foreach ($data as $columnName => $value) {
  200.             $set[] = $this->quoteIdentifier($columnName) . ' = ?';
  201.             $paramValues[] = $value;
  202.         }
  203.         $sql 'UPDATE ' $table ' SET ' implode(', '$set);
  204.         if ($where) {
  205.             $sql .= ' WHERE ' $where;
  206.         }
  207.         return $this->executeUpdate($sql$paramValues);
  208.     }
  209.     /**
  210.      * Fetches the first row of the SQL result.
  211.      *
  212.      * @param string $sql
  213.      * @param array|scalar $params
  214.      * @param array $types
  215.      *
  216.      * @return mixed
  217.      *
  218.      * @throws DBALException
  219.      */
  220.     public function fetchRow($sql$params = [], $types = [])
  221.     {
  222.         $params $this->prepareParams($params);
  223.         return $this->fetchAssociative($sql$params$types);
  224.     }
  225.     /**
  226.      * Fetches the first column of all SQL result rows as an array.
  227.      *
  228.      * @param string $sql
  229.      * @param array|scalar $params
  230.      * @param array $types
  231.      *
  232.      * @return mixed
  233.      *
  234.      * @throws DBALException
  235.      * @throws DriverException
  236.      */
  237.     public function fetchCol($sql$params = [], $types = [])
  238.     {
  239.         $params $this->prepareParams($params);
  240.         // unfortunately Mysqli driver doesn't support \PDO::FETCH_COLUMN, so we have to do it manually
  241.         $stmt $this->executeQuery($sql$params$types);
  242.         $data = [];
  243.         if ($stmt instanceof Result) {
  244.             while ($row $stmt->fetchOne()) {
  245.                 $data[] = $row;
  246.             }
  247.             $stmt->free();
  248.         }
  249.         return $data;
  250.     }
  251.     /**
  252.      * Fetches the first column of the first row of the SQL result.
  253.      *
  254.      * @param string $sql
  255.      * @param array|scalar $params
  256.      * @param array $types
  257.      *
  258.      * @return mixed
  259.      *
  260.      * @throws DBALException
  261.      */
  262.     public function fetchOne($sql$params = [], $types = [])
  263.     {
  264.         $params $this->prepareParams($params);
  265.         // unfortunately Mysqli driver doesn't support \PDO::FETCH_COLUMN, so we have to use $this->fetchColumn() instead
  266.         return parent::fetchOne($sql$params$types);
  267.     }
  268.     /**
  269.      * Fetches all SQL result rows as an array of key-value pairs.
  270.      *
  271.      * The first column is the key, the second column is the
  272.      * value.
  273.      *
  274.      * @param string $sql
  275.      * @param array $params
  276.      * @param array $types
  277.      *
  278.      * @return array
  279.      *
  280.      * @throws DBALException
  281.      * @throws DriverException
  282.      */
  283.     public function fetchPairs($sql, array $params = [], $types = [])
  284.     {
  285.         $params $this->prepareParams($params);
  286.         $stmt $this->executeQuery($sql$params$types);
  287.         $data = [];
  288.         if ($stmt instanceof Result) {
  289.             while ($row $stmt->fetchNumeric()) {
  290.                 $data[$row[0]] = $row[1];
  291.             }
  292.         }
  293.         return $data;
  294.     }
  295.     /**
  296.      * @param string $table
  297.      * @param array $data
  298.      *
  299.      * @return int
  300.      *
  301.      * @throws DBALException
  302.      */
  303.     public function insertOrUpdate($table, array $data)
  304.     {
  305.         // extract and quote col names from the array keys
  306.         $i 0;
  307.         $bind = [];
  308.         $cols = [];
  309.         $vals = [];
  310.         foreach ($data as $col => $val) {
  311.             $cols[] = $this->quoteIdentifier($col);
  312.             $bind[':col' $i] = $val;
  313.             $vals[] = ':col' $i;
  314.             $i++;
  315.         }
  316.         // build the statement
  317.         $set = [];
  318.         foreach ($cols as $i => $col) {
  319.             $set[] = sprintf('%s = %s'$col$vals[$i]);
  320.         }
  321.         $sql sprintf(
  322.             'INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s;',
  323.             $this->quoteIdentifier($table),
  324.             implode(', '$cols),
  325.             implode(', '$vals),
  326.             implode(', '$set)
  327.         );
  328.         $bind array_merge($bind$bind);
  329.         return $this->executeUpdate($sql$bind);
  330.     }
  331.     /**
  332.      * Quotes a value and places into a piece of text at a placeholder.
  333.      *
  334.      * The placeholder is a question-mark; all placeholders will be replaced
  335.      * with the quoted value.   For example:
  336.      *
  337.      * <code>
  338.      * $text = "WHERE date < ?";
  339.      * $date = "2005-01-02";
  340.      * $safe = $sql->quoteInto($text, $date);
  341.      * // $safe = "WHERE date < '2005-01-02'"
  342.      * </code>
  343.      *
  344.      * @param string $text The text with a placeholder.
  345.      * @param mixed $value The value to quote.
  346.      * @param string|null $type OPTIONAL SQL datatype
  347.      * @param int|null $count OPTIONAL count of placeholders to replace
  348.      *
  349.      * @return string An SQL-safe quoted value placed into the original text.
  350.      */
  351.     public function quoteInto($text$value$type null$count null)
  352.     {
  353.         if ($count === null) {
  354.             return str_replace('?'$this->quote($value$type), $text);
  355.         }
  356.         return implode($this->quote($value$type), explode('?'$text$count 1));
  357.     }
  358.     /**
  359.      * Quote a column identifier and alias.
  360.      *
  361.      * @param string|array $ident The identifier or expression.
  362.      * @param string|null $alias An alias for the column.
  363.      *
  364.      * @return string The quoted identifier and alias.
  365.      */
  366.     public function quoteColumnAs($ident$alias null)
  367.     {
  368.         return $this->_quoteIdentifierAs($ident$alias);
  369.     }
  370.     /**
  371.      * Quote a table identifier and alias.
  372.      *
  373.      * @param string|array $ident The identifier or expression.
  374.      * @param string|null $alias An alias for the table.
  375.      *
  376.      * @return string The quoted identifier and alias.
  377.      */
  378.     public function quoteTableAs($ident$alias null)
  379.     {
  380.         return $this->_quoteIdentifierAs($ident$alias);
  381.     }
  382.     /**
  383.      * Quote an identifier and an optional alias.
  384.      *
  385.      * @param string|array $ident The identifier or expression.
  386.      * @param string|null $alias An optional alias.
  387.      * @param bool $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
  388.      * @param string $as The string to add between the identifier/expression and the alias.
  389.      *
  390.      * @return string The quoted identifier and alias.
  391.      */
  392.     protected function _quoteIdentifierAs($ident$alias null$auto false$as ' AS ')
  393.     {
  394.         if (is_string($ident)) {
  395.             $ident explode('.'$ident);
  396.         }
  397.         if (is_array($ident)) {
  398.             $segments = [];
  399.             foreach ($ident as $segment) {
  400.                 $segments[] = $this->_quoteIdentifier($segment$auto);
  401.             }
  402.             if ($alias !== null && end($ident) == $alias) {
  403.                 $alias null;
  404.             }
  405.             $quoted implode('.'$segments);
  406.         } else {
  407.             $quoted $this->_quoteIdentifier($ident$auto);
  408.         }
  409.         if ($alias !== null) {
  410.             $quoted .= $as $this->_quoteIdentifier($alias$auto);
  411.         }
  412.         return $quoted;
  413.     }
  414.     /**
  415.      * Quote an identifier.
  416.      *
  417.      * @param string $value The identifier or expression.
  418.      * @param bool $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
  419.      *
  420.      * @return string The quoted identifier and alias.
  421.      */
  422.     protected function _quoteIdentifier($value$auto false)
  423.     {
  424.         if ($auto === false) {
  425.             $q '`';
  426.             return $q str_replace((string) $q"$q$q"$value) . $q;
  427.         }
  428.         return $value;
  429.     }
  430.     /**
  431.      * Adds an adapter-specific LIMIT clause to the SELECT statement.
  432.      *
  433.      * @param string $sql
  434.      * @param int $count
  435.      * @param int $offset OPTIONAL
  436.      *
  437.      * @throws \Exception
  438.      *
  439.      * @return string
  440.      */
  441.     public function limit($sql$count$offset 0)
  442.     {
  443.         $count = (int) $count;
  444.         if ($count <= 0) {
  445.             throw new \Exception("LIMIT argument count=$count is not valid");
  446.         }
  447.         $offset = (int) $offset;
  448.         if ($offset 0) {
  449.             throw new \Exception("LIMIT argument offset=$offset is not valid");
  450.         }
  451.         $sql .= " LIMIT $count";
  452.         if ($offset 0) {
  453.             $sql .= " OFFSET $offset";
  454.         }
  455.         return $sql;
  456.     }
  457.     /**
  458.      * @param string $sql
  459.      * @param array $exclusions
  460.      *
  461.      * @return ResultStatement|null
  462.      *
  463.      * @throws ValidationException
  464.      */
  465.     public function queryIgnoreError($sql$exclusions = [])
  466.     {
  467.         try {
  468.             return $this->query($sql);
  469.         } catch (\Exception $e) {
  470.             foreach ($exclusions as $exclusion) {
  471.                 if ($e instanceof $exclusion) {
  472.                     throw new ValidationException($e->getMessage(), 0$e);
  473.                 }
  474.             }
  475.             // we simply ignore the error
  476.         }
  477.         return null;
  478.     }
  479.     /**
  480.      * @param array|scalar $params
  481.      *
  482.      * @return array
  483.      */
  484.     protected function prepareParams($params)
  485.     {
  486.         if (is_scalar($params)) {
  487.             $params = [$params];
  488.         }
  489.         return $params;
  490.     }
  491.     /**
  492.      * @param array $data
  493.      *
  494.      * @return array
  495.      */
  496.     protected function quoteDataIdentifiers($data)
  497.     {
  498.         if (!$this->autoQuoteIdentifiers) {
  499.             return $data;
  500.         }
  501.         $newData = [];
  502.         foreach ($data as $key => $value) {
  503.             $newData[$this->quoteIdentifier($key)] = $value;
  504.         }
  505.         return $newData;
  506.     }
  507.     /**
  508.      * @param bool $autoQuoteIdentifiers
  509.      */
  510.     public function setAutoQuoteIdentifiers($autoQuoteIdentifiers)
  511.     {
  512.         $this->autoQuoteIdentifiers $autoQuoteIdentifiers;
  513.     }
  514.     /**
  515.      * @param string $table
  516.      * @param string $idColumn
  517.      * @param string $where
  518.      */
  519.     public function selectAndDeleteWhere($table$idColumn 'id'$where '')
  520.     {
  521.         $sql 'SELECT ' $this->quoteIdentifier($idColumn) . '  FROM ' $table;
  522.         if ($where) {
  523.             $sql .= ' WHERE ' $where;
  524.         }
  525.         $idsForDeletion $this->fetchCol($sql);
  526.         if (!empty($idsForDeletion)) {
  527.             $chunks array_chunk($idsForDeletion1000);
  528.             foreach ($chunks as $chunk) {
  529.                 $idString implode(','array_map([$this'quote'], $chunk));
  530.                 $this->deleteWhere($table$idColumn ' IN (' $idString ')');
  531.             }
  532.         }
  533.     }
  534.     /**
  535.      * @param string $like
  536.      *
  537.      * @return string
  538.      */
  539.     public function escapeLike(string $like): string
  540.     {
  541.         return str_replace(['_''%'], ['\\_''\\%'], $like);
  542.     }
  543. }