xpdo = $xpdo; } /** * * Rebuilds a statement with array values replaced into placeholders. * * @param string $statement The statement to rebuild. * * @param array $values The values to bind and/or replace into a statement. * * @return array An array where element 0 is the rebuilt statement and * element 1 is the rebuilt array of values. * */ public function __invoke($statement, $values) { // match standard PDO execute() behavior of zero-indexed arrays if (array_key_exists(0, $values)) { array_unshift($values, null); } $this->values = $values; $statement = $this->rebuildStatement($statement); return array($statement, $this->final_values); } /** * * Given a statement, rebuilds it with array values embedded. * * @param string $statement The SQL statement. * * @return string The rebuilt statement. * */ protected function rebuildStatement($statement) { // find all parts not inside quotes or backslashed-quotes $apos = "'"; $quot = '"'; $parts = preg_split( "/(($apos+|$quot+|\\$apos+|\\$quot+).*?)\\2/m", $statement, -1, PREG_SPLIT_DELIM_CAPTURE ); return $this->rebuildParts($parts); } /** * * Given an array of statement parts, rebuilds each part. * * @param array $parts The statement parts. * * @return string The rebuilt statement. * */ protected function rebuildParts($parts) { // loop through the non-quoted parts (0, 3, 6, 9, etc.) $k = count($parts); for ($i = 0; $i <= $k; $i += 3) { $parts[$i] = $this->rebuildPart($parts[$i]); } return implode('', $parts); } /** * * Rebuilds a single statement part. * * @param string $part The statement part. * * @return string The rebuilt statement. * */ protected function rebuildPart($part) { // split into subparts by ":name" and "?" $subs = preg_split( "/(:[a-zA-Z_][a-zA-Z0-9_]*)|(\?)/m", $part, -1, PREG_SPLIT_DELIM_CAPTURE ); // check subparts to convert bound arrays to quoted CSV strings $subs = $this->prepareValuePlaceholders($subs); // reassemble return implode('', $subs); } /** * * Prepares the sub-parts of a query with placeholders. * * @param array $subs The query subparts. * * @return array The prepared subparts. * */ protected function prepareValuePlaceholders(array $subs) { foreach ($subs as $i => $sub) { // is the subpart a ?-mark? if ($sub == '?') { // prepare as numbered placeholder $subs[$i] = $this->prepareNumberedPlaceholder($sub); } // is the subpart only a colon? if ($sub == ':') { // ignore it continue; } // does the subpart begin with a colon? $char = substr($sub, 0, 1); if ($char != ':') { // no, so cannot be named placeholder continue; } // does previous subpart (if there is one) end in a colon? if ($i > 0 && substr($subs[$i - 1], -1) == ':') { // prev ended in ':', and current begins with ':', making it a // double-colon, so ignore it continue; } // begins with colon, previous did not end with colon, so prepare // as a named placeholder $subs[$i] = $this->prepareNamedPlaceholder($sub); } return $subs; } /** * * Bind or quote a numbered placeholder in a query subpart. * * @param string $sub The query subpart. * * @return string The prepared query subpart. * * @throws MissingParameter */ protected function prepareNumberedPlaceholder($sub) { // what numbered placeholder is this in the original statement? $this->num ++; // is the corresponding data element an array? $bind_array = isset($this->values[$this->num]) && is_array($this->values[$this->num]); if ($bind_array) { // PDO won't bind an array; quote and replace directly $sub = $this->xpdo->quote($this->values[$this->num]); } else { // increase the count of numbered placeholders to be bound $this->count ++; if (array_key_exists($this->num, $this->values) === false) { throw new MissingParameter('Parameter ' . $this->num . ' is missing from the bound values'); } $this->final_values[$this->count] = $this->values[$this->num]; } return $sub; } /** * * Bind or quote a named placeholder in a query subpart. * * @param string $sub The query subpart. * * @return string The prepared query subpart. * */ protected function prepareNamedPlaceholder($sub) { $name = substr($sub, 1); // is the corresponding data element an array? $bind_array = isset($this->values[$name]) && is_array($this->values[$name]); if ($bind_array) { // PDO won't bind an array; quote and replace directly $sub = $this->xpdo->quote($this->values[$name]); } else { // not an array, retain the placeholder for later $this->final_values[$name] = $this->values[$name]; } return $sub; } }