Advertisement
Guest User

MySQLi statement wrapper

a guest
May 24th, 2011
238
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 27.57 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4.  * This file is part of the Carrot framework.
  5.  *
  6.  * Copyright (c) 2011 Ricky Christie <seven.rchristie@gmail.com>
  7.  *
  8.  * Licensed under the MIT License.
  9.  *
  10.  */
  11.  
  12. /**
  13.  * Wrapper for MySQLi_STMT
  14.  *
  15.  * Creates a MySQLi_STMT object and acts as its wrapper, allowing you to use
  16.  * prepared statements without dealing with parameter and result row binding.
  17.  * It also wraps around the confusing '?' with named placeholder, which makes
  18.  * reading and editing prepared statements much easier when your query has
  19.  * more than 15 parameters.
  20.  *
  21.  * Placeholders are marked with hash (#) character at the beginning and
  22.  * detected with this regular expression:
  23.  *
  24.  * <code>
  25.  * #[a-zA-Z0-9_#]+
  26.  * </code>
  27.  *
  28.  * Example of allowed placeholders:
  29.  *
  30.  * <code>
  31.  * #placeholder
  32.  * #place_holder
  33.  * ##Placeholder
  34.  * #123
  35.  * </code>
  36.  *
  37.  * At object construction, placeholders will be replaced with '?' and used
  38.  * to construct an instance of MySQLi_STMT:
  39.  *
  40.  * <code>
  41.  * $mysqli = new MySQLi('localhost', 'user', 'pwd', 'testdb');
  42.  * $statement = new Carrot\Database\MySQLi\StatementWrapper($mysqli,
  43.  *     'SELECT
  44.  *         id, name, balance
  45.  *      FROM
  46.  *         accounts
  47.  *      WHERE
  48.  *         name LIKE #name_like,
  49.  *         balance > #balance_lower_limit'
  50.  * );
  51.  * </code>
  52.  *
  53.  * You can then execute by passing associative array as parameter. The class
  54.  * will check for integers and floats and mark their type accordingly. Default
  55.  * parameter type is string.
  56.  *
  57.  * <code>
  58.  * $params = array
  59.  * (
  60.  *     '#name_like' => 'John%',
  61.  *     '#balance_lower_limit' => 25000
  62.  * );
  63.  *
  64.  * $statement->execute($params);
  65.  * </code>
  66.  *
  67.  * If you need to mark a parameter as blob, use this method before execution:
  68.  *
  69.  * <code>
  70.  * $statement->markParamAsBlob('#blob_param');
  71.  * </code>
  72.  *
  73.  * Fetching results is done using a while loop, you can fetch as enumerated
  74.  * array, associative array, or an object:
  75.  *
  76.  * <code>
  77.  * while ($row = $statement->fetchObject())
  78.  * {
  79.  *     echo "ID: {$row->id}";
  80.  * }
  81.  * </code>
  82.  *
  83.  * Default behavior is to return false when execution fails. You can tell the
  84.  * object to throw an exception when execution fails using this method:
  85.  *
  86.  * <code>
  87.  * $statement->throwExceptionWhenExecutionFails(true);
  88.  * </code>
  89.  *
  90.  * @author      Ricky Christie <seven.rchristie@gmail.com>
  91.  * @license     http://www.opensource.org/licenses/mit-license.php MIT License
  92.  *
  93.  */
  94.  
  95. namespace Carrot\Database\MySQLi;
  96.  
  97. class StatementWrapper
  98. {
  99.     /**
  100.      * @var string Statement string with placeholders, injected during construction.
  101.      */
  102.     protected $statement_string_with_placeholders;
  103.    
  104.     /**
  105.      * @var string Processed statement string, used in constructing the MySQLi_STMT object, placeholders replaced with '?'.
  106.      */
  107.     protected $statement_string;
  108.    
  109.     /**
  110.      * @var MySQLi_STMT Instance of MySQLi_STMT, constructed using {@see $statement_string}.
  111.      */
  112.     protected $statement_object;
  113.    
  114.     /**
  115.      * @var array List of placeholders with the hash (#) prefix, extracted from {@see $statement_string_with_placeholders}.
  116.      */
  117.     protected $placeholders;
  118.    
  119.     /**
  120.      * @var array List of placeholders with 'blob' data type, set by the user - see {@see }.
  121.      */
  122.     protected $blob_params = array();
  123.    
  124.     /**
  125.      * @var mixed Contains the result of MySQLi_STMT::result_metadata() call.
  126.      */
  127.     protected $result_metadata;
  128.    
  129.     /**
  130.      * @var array Parameters used to execute the query.
  131.      */
  132.     protected $params;
  133.    
  134.     /**
  135.      * @var string Parameter types in string, as per MySQLi_STMT::bind_param() specification.
  136.      */
  137.     protected $param_types;
  138.    
  139.     /**
  140.      * @var array Result row, filled with new values every time a new row is fetched.
  141.      */
  142.     protected $result_row;
  143.    
  144.     /**
  145.      * @var array Contains references to the {@see $params} property, used for binding in bind_param().
  146.      */
  147.     protected $references_params;
  148.    
  149.     /**
  150.      * @var array Contains references to the {@see $result_row} property, used for binding in bind_result().
  151.      */
  152.     protected $references_result_row;
  153.    
  154.     /**
  155.      * @var bool If set to true, any subsequent execution that fails/returns false will trigger an exception.
  156.      */
  157.     protected $throw_exception_when_execution_fails = false;
  158.    
  159.     /**
  160.      * @var bool True if result set has been buffered using MySQLi_STMT::store_result(), false otherwise.
  161.      */
  162.     protected $result_is_buffered = false;
  163.    
  164.     /**
  165.      * Constructs an instance of the StatementWrapper.
  166.      *
  167.      * Assumes that the MySQLi instance is already connected. Statement
  168.      * string sent must use placeholders. Placeholders are marked by the
  169.      * hash character (#).
  170.      *
  171.      * <code>
  172.      * $statement = new StatementWrapper($mysqli,
  173.      *     'SELECT
  174.      *         id, name, balance
  175.      *     FROM
  176.      *         accounts
  177.      *     WHERE
  178.      *         id LIKE #id,
  179.      *         name LIKE #name,
  180.      *         balance > #balance'
  181.      * );
  182.      * </code>
  183.      *
  184.      * The statement string from above code will be converted to:
  185.      *
  186.      * <code>
  187.      * SELECT
  188.      *     id, name, balance
  189.      * FROM
  190.      *     accounts
  191.      * WHERE
  192.      *     id LIKE ?,
  193.      *     name LIKE ?,
  194.      *     balance > ?
  195.      * </code>
  196.      *
  197.      * with these placeholders:
  198.      *
  199.      * <code>
  200.      * #id
  201.      * #name
  202.      * #balance
  203.      * </code>
  204.      *
  205.      * @param MySQLi $mysqli Instance of MySQLi (must be already connected).
  206.      * @param string $statement_string String containing the statement (must use placeholders).
  207.      *
  208.      */
  209.     public function __construct(\MySQLi $mysqli, $statement_string_with_placeholders)
  210.     {
  211.         $this->statement_string_with_placeholders = $statement_string_with_placeholders;
  212.         $this->placeholders = $this->extractPlaceholders($statement_string_with_placeholders);
  213.         $this->statement_string = $this->replacePlaceholdersWithQuestionMarks($statement_string_with_placeholders);
  214.         $this->statement_object = $mysqli->prepare($this->statement_string);
  215.        
  216.         if (empty($this->statement_object) or !is_a($this->statement_object, '\MySQLi_STMT'))
  217.         {
  218.             throw new \RuntimeException("StatementWrapper error, fails to prepare the statement. Error number: '{$mysqli->errno}', Error message: '{$mysqli->error}', Processed statement: '{$this->statement_string}', Original statement: '{$this->statement_string_with_placeholders}'.");
  219.         }
  220.        
  221.         $this->result_metadata = $this->statement_object->result_metadata();
  222.         $this->createParameterVariablesAndReferences();
  223.         $this->createResultVariablesAndReferences();
  224.         $this->bindResult();
  225.     }
  226.    
  227.     /**
  228.      * Executes the statement.
  229.      *
  230.      * Pass the parameters as associative array. Previously used
  231.      * parameters will be used if you don't pass parameter array.
  232.      * You don't need to pass anything if your statement doesn't
  233.      * need parameters.
  234.      *
  235.      * <code>
  236.      * $statement = new StatementWrapper($mysqli, 'INSERT INTO accounts (id, first_name) VALUES (#id, #first_name));
  237.      * $statement->execute(array('#id' => 'AB12345', '#first_name' => 'John'));
  238.      * </code>
  239.      *
  240.      * Will throw RuntimeException if execution fails and
  241.      * $throw_exception_when_execution_fails is true.
  242.      *
  243.      * @throws RuntimeException
  244.      * @see $throw_exception_when_execution_fails
  245.      * @param array $params Optional. Parameters to use for execution, if left empty will use previously set parameters.
  246.      * @return bool Returns true if statement executed successfully, false otherwise.
  247.      *
  248.      */
  249.     public function execute(array $params = array())
  250.     {
  251.         if (!empty($params))
  252.         {
  253.             $this->setAndBindParameters($params);
  254.         }
  255.        
  256.         $result = $this->statement_object->execute();
  257.        
  258.         if (!$result && $this->throw_exception_when_execution_fails)
  259.         {
  260.             throw new \RuntimeException("StatementWrapper execution error! Error #{$this->statement_object->errno}: '{$this->statement_object->error}', statement is '{$this->statement_string}'.");
  261.         }
  262.        
  263.         // After each execution, you need to call MySQLi_STMT::store_result() again.
  264.         $this->result_is_buffered = false;
  265.        
  266.         return $result;
  267.     }
  268.    
  269.     /**
  270.      * Fetches the result as enumerated array using MySQLi_STMT::fetch().
  271.      *
  272.      * Calls to this method is ignored if the statement doesn't have
  273.      * result. Use while() loop to iterate the result set:
  274.      *
  275.      * <code>
  276.      * while ($row = $statement->fetchArray())
  277.      * {
  278.      *     echo "ID: {$row[0]}, Name: {$row[1]}";
  279.      * }
  280.      * </code>
  281.      *
  282.      * @return mixed Result row as enumerated array. False if no more rows or failure in fetching.
  283.      *
  284.      */
  285.     public function fetchArray()
  286.     {
  287.         if (is_object($this->result_metadata) && is_a($this->result_metadata, '\MySQLi_Result'))
  288.         {
  289.             $result = $this->statement_object->fetch();
  290.            
  291.             if ($result === true)
  292.             {
  293.                 $row = array();
  294.                
  295.                 foreach ($this->result_row as $content)
  296.                 {
  297.                     $row[] = $content;
  298.                 }
  299.                
  300.                 return $row;
  301.             }
  302.            
  303.             return false;
  304.         }
  305.     }
  306.    
  307.     /**
  308.      * Fetches the result as associative array using MySQLi_STMT::fetch().
  309.      *
  310.      * Calls to this method is ignored if the statement doesn't have
  311.      * result. Use while() loop to iterate the result set:
  312.      *
  313.      * <code>
  314.      * while ($row = $statement->fetchAssociativeArray())
  315.      * {
  316.      *     echo "ID: {$row['id']}, Name: {$row['name']}";
  317.      * }
  318.      * </code>
  319.      *
  320.      * @return mixed Result row as associative array. False if no more rows or failure in fetching.
  321.      *
  322.      */
  323.     public function fetchAssociativeArray()
  324.     {
  325.         if (is_object($this->result_metadata) && is_a($this->result_metadata, '\MySQLi_Result'))
  326.         {
  327.             $result = $this->statement_object->fetch();
  328.            
  329.             if ($result === true)
  330.             {
  331.                 $row = array();
  332.                
  333.                 foreach ($this->result_row as $field_name => $content)
  334.                 {
  335.                     $row[$field_name] = $content;
  336.                 }
  337.                
  338.                 return $row;
  339.             }
  340.            
  341.             return false;
  342.         }
  343.     }
  344.    
  345.     /**
  346.      * Fetches the result as PHP standard object using MySQLi_STMT::fetch().
  347.      *
  348.      * Calls to this method is ignored if the statement doesn't have
  349.      * result. Use while() loop to iterate the result set:
  350.      *
  351.      * <code>
  352.      * while ($row = $statement->fetchObject())
  353.      * {
  354.      *     echo "ID: {$row->id}, Name: {$row->name}";
  355.      * }
  356.      * </code>
  357.      *
  358.      * @return mixed Result row as PHP standard object. False if no more rows or failure in fetching.
  359.      *
  360.      */
  361.     public function fetchObject()
  362.     {
  363.         if (is_object($this->result_metadata) && is_a($this->result_metadata, '\MySQLi_Result'))
  364.         {
  365.             $result = $this->statement_object->fetch();
  366.            
  367.             if ($result === true)
  368.             {
  369.                 $row = array();
  370.                
  371.                 foreach ($this->result_row as $field_name => $content)
  372.                 {
  373.                     $row[$field_name] = $content;
  374.                 }
  375.                
  376.                 return (object) $row;
  377.             }
  378.            
  379.             return false;
  380.         }
  381.     }
  382.    
  383.     /**
  384.      * Mark parameter placeholder as 'blob' type.
  385.      *
  386.      * For each statement execution, parameters are automatically
  387.      * assigned proper type by detecting the parameter variable type
  388.      * using is_integer(), is_float(), and is_string(). Parameter type
  389.      * defaults to string. If you have to send a blob parameter type,
  390.      * use this method to mark the placeholder as such.
  391.      *
  392.      * <code>
  393.      * $statement->markParamAsBlob('#blob_param');
  394.      * </code>
  395.      *
  396.      * @see $blob_params
  397.      * @param string $placeholder The placeholder you want to mark as blob, with hash (#).
  398.      *
  399.      */
  400.     public function markParamAsBlob($placeholder)
  401.     {
  402.         if (!isset($this->placeholders[$placeholder]))
  403.         {
  404.             throw new \RuntimeException("StatementWrapper error in marking parameter as blob. Placeholder '{$placeholder}' is not defined.");
  405.         }
  406.        
  407.         $this->blob_params[] = $placeholder;
  408.     }
  409.    
  410.     /**
  411.      * Tells the class to throw/not to throw exceptions when statement execution fails.
  412.      *
  413.      * Default behavior is to NOT throw exception when the query fails
  414.      * and simply return false. This makes it easier for single statements,
  415.      * however if you need to craft a transaction, you can tell this
  416.      * class to throw exception if execution fails (for whatever reason).
  417.      *
  418.      * <code>
  419.      * $statement->throwExceptionWhenExecutionFails(true);
  420.      * </code>
  421.      *
  422.      * @param bool $bool Pass true to throw exceptions, false otherwise.
  423.      *
  424.      */
  425.     public function throwExceptionWhenExecutionFails($bool)
  426.     {
  427.         $this->throw_exception_when_execution_fails = $bool;
  428.     }
  429.    
  430.     /**
  431.      * See if the result set is buffered or not.
  432.      *
  433.      * The result set is buffered if MySQLi_STMT::store_result() is
  434.      * called after each statement execution. The wrapper notes this
  435.      * by setting $result_is_buffered property to true every time
  436.      * MySQLi_STMT::store_result() is called.
  437.      *
  438.      * The wrapper does not buffer the result by default, following
  439.      * MySQLi_STMT standard behavior.
  440.      *
  441.      * If the result set is not buffered, MySQLi_STMT->num_rows() will
  442.      * not return a valid response.
  443.      *
  444.      * @return bool True if buffered, false otherwise.
  445.      *
  446.      */
  447.     public function resultIsBuffered()
  448.     {
  449.         return $this->result_is_buffered;
  450.     }
  451.    
  452.     /**
  453.      * Returns the result metadata.
  454.      *
  455.      * This method does not wrap/call MySQLi_STMT::result_metadata(),
  456.      * it simply returns a saved value since MySQLi_STMT::result_metadata()
  457.      * is already called in construction.
  458.      *
  459.      * @return mixed Instance of MySQLi_Result or false if there isn't a result.
  460.      *
  461.      */
  462.     public function getResultMetadata()
  463.     {
  464.         return $this->result_metadata;
  465.     }
  466.    
  467.     /**
  468.      * Destroys this object.
  469.      *
  470.      * Calls MySQLi_STMT::close() for safety.
  471.      *
  472.      */
  473.     public function __destruct()
  474.     {
  475.         $this->result_is_buffered = false;
  476.         $this->statement_object->close();
  477.     }
  478.    
  479.     // ---------------------------------------------------------------
  480.    
  481.     /**
  482.      * Wrapper for MySQLi_STMT->affected_rows.
  483.      *
  484.      * @return mixed -1 indicates query error.
  485.      *
  486.      */
  487.     public function getAffectedRows()
  488.     {
  489.         return $this->statement_object->affected_rows;
  490.     }
  491.    
  492.     /**
  493.      * Wrapper for MySQLi_STMT::attr_get().
  494.      *
  495.      * @param int $attr The attribute you want to get.
  496.      * @return mixed False if the attribute is not found, otherwise return value of the attribute.
  497.      *
  498.      */
  499.     public function getAttr($attr)
  500.     {
  501.         return $this->statement_object->attr_get($attr);
  502.     }
  503.    
  504.     /**
  505.      * Wrapper for MySQLi_STMT::attr_set().
  506.      *
  507.      * @param int $attr The attribute you want to set.
  508.      * @param int $mode The value to assign to the attribute.
  509.      *
  510.      */
  511.     public function setAttr($attr, $mode)
  512.     {
  513.         $this->statement_object->attr_set($attr, $mode);
  514.     }
  515.    
  516.     /**
  517.      * Wrapper for MySQLi_STMT::data_seek().
  518.      *
  519.      * @param int $offset
  520.      *
  521.      */
  522.     public function dataSeek($offset)
  523.     {
  524.         $this->statement_object->data_seek($offset);
  525.     }
  526.    
  527.     /**
  528.      * Wrapper for MySQLi_STMT->errno.
  529.      *
  530.      * @return int Error number for the last execution.
  531.      *
  532.      */
  533.     public function getErrorNo()
  534.     {
  535.         return $this->statement_object->errno;
  536.     }
  537.    
  538.     /**
  539.      * Wrapper for MySQLi_STMT->error.
  540.      *
  541.      * @return string Error message for last execution.
  542.      *
  543.      */
  544.     public function getErrorMessage()
  545.     {
  546.         return $this->statement_object->error;
  547.     }
  548.    
  549.     /**
  550.      * Wrapper for MySQLi_STMT->field_count.
  551.      *
  552.      * @return int Number of fields in the given statement.
  553.      *
  554.      */
  555.     public function getFieldCount()
  556.     {
  557.         return $this->statement_object->field_count;
  558.     }
  559.    
  560.     /**
  561.      * Wrapper for MySQLi_STMT::free_result().
  562.      *
  563.      * This method also notes that result buffer has been cleared by
  564.      * setting $result_is_buffered property to false.
  565.      *
  566.      * When you run a prepared statement that returns a result set, it
  567.      * locks the connection unless you free_result() or store_result().
  568.      *
  569.      */
  570.     public function freeResult()
  571.     {
  572.         $this->statement_object->free_result();
  573.         $this->result_is_buffered = false;
  574.     }
  575.    
  576.     /**
  577.      * Wrapper for MySQLi_STMT::get_warnings().
  578.      *
  579.      * @return mixed
  580.      *
  581.      */
  582.     public function getWarnings()
  583.     {
  584.         return $this->statement_object->get_warnings();
  585.     }
  586.    
  587.     /**
  588.      * Wrapper for MySQLi_STMT->insert_id.
  589.      *
  590.      * @return int The ID generated from previous INSERT operation.
  591.      *
  592.      */
  593.     public function getInsertID()
  594.     {
  595.         return $this->statement_object->insert_id;
  596.     }
  597.    
  598.     /**
  599.      * Wrapper for MySQLi_STMT->num_rows.
  600.      *
  601.      * This method does not return invalid row count, it returns false
  602.      * if result set is not buffered.
  603.      *
  604.      * @return mixed Number of rows if result is buffered, false if result set is not buffered.
  605.      *
  606.      */
  607.     public function getNumRows()
  608.     {
  609.         if ($this->result_is_buffered)
  610.         {
  611.             return $this->statement_object->num_rows;
  612.         }
  613.        
  614.         return false;
  615.     }
  616.    
  617.     /**
  618.      * Wrapper for MySQLi_STMT->param_count.
  619.      *
  620.      * @return int $param_count Number of parameters in the statement.
  621.      *
  622.      */
  623.     public function getParamCount()
  624.     {
  625.         return $this->statement_object->param_count;
  626.     }
  627.    
  628.     /**
  629.      * Wrapper for MySQLi_STMT::reset().
  630.      *
  631.      * MySQLi_STMT::reset does not unbind parameter. After you reset, you
  632.      * can safely execute it again even if the query has parameters.
  633.      *
  634.      * @return bool True on success, false on failure.
  635.      *
  636.      */
  637.     public function reset()
  638.     {
  639.         return $this->statement_object->reset();
  640.     }
  641.    
  642.     /**
  643.      * Wrapper for MySQLi_STMT->sqlstate.
  644.      *
  645.      * @return string SQLSTATE error from previous statement operation.
  646.      *
  647.      */
  648.     public function getSQLState()
  649.     {
  650.         return $this->statement_object->sqlstate;
  651.     }
  652.    
  653.     /**
  654.      * Wrapper for MySQLi_STMT::store_result().
  655.      *
  656.      * This method also sets $result_is_buffered property to true,
  657.      * allowing you getNumRows() method to return valid value. This
  658.      * method must be called *after* execution.
  659.      *
  660.      * @return bool True on success, false on failure.
  661.      *
  662.      */
  663.     public function storeResult()
  664.     {
  665.         $this->result_is_buffered = $this->statement_object->store_result();
  666.         return $this->result_is_buffered;
  667.     }
  668.    
  669.     // ---------------------------------------------------------------
  670.    
  671.     /**
  672.      * Extracts placeholder names from original statement string.
  673.      *
  674.      * Placeholder is defined with this regular expression:
  675.      *
  676.      * <code>
  677.      * #[a-zA-Z0-9_#]+
  678.      * </code>
  679.      *
  680.      * Since the hash character (#) is used in MySQL to mark comments,
  681.      * chances are you won't be using it in your query other than for
  682.      * marking placeholders. List of example placeholder that will
  683.      * match:
  684.      *
  685.      * <code>
  686.      * #placeholder
  687.      * #123placeholder
  688.      * #_place_holder
  689.      * ##placeholder
  690.      * #place#holder
  691.      * </code>
  692.      *
  693.      * @param string $statement_string_with_placeholders
  694.      * @return array Array that contains placeholder names.
  695.      *
  696.      */
  697.     protected function extractPlaceholders($statement_string_with_placeholders)
  698.     {
  699.         preg_match_all('/#[a-zA-Z0-9_#]+/', $statement_string_with_placeholders, $matches);
  700.        
  701.         if (isset($matches[0]) && is_array($matches[0]))
  702.         {
  703.             return $matches[0];
  704.         }
  705.        
  706.         return array();
  707.     }
  708.    
  709.     /**
  710.      * Replaces placeholders (#string) with '?'.
  711.      *
  712.      * This in effect creates a statement string that we can use it
  713.      * to instantiate a MySQLi statement object. It replaces this
  714.      * pattern:
  715.      *
  716.      * <code>
  717.      * #[a-zA-Z0-9_#]+
  718.      * </code>
  719.      *
  720.      * with question mark ('?'). Returns empty array if no placeholder
  721.      * is found.
  722.      *
  723.      * @param string $statement_string_with_placeholders
  724.      * @return string Statement string safe to use as MySQLi_STMT instantiation argument.
  725.      *
  726.      */
  727.     protected function replacePlaceholdersWithQuestionMarks($statement_string_with_placeholders)
  728.     {
  729.         return preg_replace('/#[a-zA-Z0-9_#]+/', '?', $statement_string_with_placeholders);
  730.     }
  731.    
  732.     /**
  733.      * Creates parameter array to store parameters and a set of references that refers to it.
  734.      *
  735.      * We create parameter array to store parameters set by the user,
  736.      * and we create an array that references those parameters to be
  737.      * used as arguments when we use call_user_func() to call
  738.      * MySQLi_STMT::bind_param().
  739.      *
  740.      * @see $params
  741.      * @see $references_params
  742.      * @see __construct()
  743.      *
  744.      */
  745.     protected function createParameterVariablesAndReferences()
  746.     {
  747.         $placeholder_count = count($this->placeholders);
  748.        
  749.         if ($this->statement_object->param_count != $placeholder_count)
  750.         {
  751.             throw new \RuntimeException("StatementWrapper error, fails to prepare the statement. Parameter count ({$this->statement_object->param_count}) and placeholder count ({$placeholder_count}) does not match.");
  752.         }
  753.        
  754.         $this->references_params['types'] = &$this->param_types;
  755.        
  756.         foreach ($this->placeholders as $placeholder)
  757.         {
  758.             $this->params[$placeholder] = null;
  759.             $this->references_params[$placeholder] = &$this->params[$placeholder];
  760.         }
  761.     }
  762.    
  763.     /**
  764.      * Creates array to store a fetched result row and a set of references that refers to it.
  765.      *
  766.      * We create result row variables as an array to store each value
  767.      * every time we fetch using MySQLi_STMT::fetch(). We create
  768.      * references to these result row variables to be passed when we
  769.      * use call_user_func() to call MySQLi_STMT::bind_result().
  770.      *
  771.      * @see $result_row
  772.      * @see $references_result_row
  773.      * @see __construct()
  774.      *
  775.      */
  776.     protected function createResultVariablesAndReferences()
  777.     {
  778.         if (is_object($this->result_metadata) && is_a($this->result_metadata, '\MySQLi_Result'))
  779.         {
  780.             foreach ($this->result_metadata->fetch_fields() as $field)
  781.             {
  782.                 $this->result_row[$field->name] = null;
  783.                 $this->references_result_row[$field->name] = &$this->result_row[$field->name];
  784.             }
  785.         }
  786.     }
  787.    
  788.     /**
  789.      * Binds result row references using MySQLi_STMT::bind_result().
  790.      *
  791.      * We only need to bind the result once, hence this method is called
  792.      * only at the constructor.
  793.      *
  794.      * @see $result_row
  795.      * @see $references_result_row
  796.      * @see __construct()
  797.      *
  798.      */
  799.     protected function bindResult()
  800.     {
  801.         if (is_object($this->result_metadata) && is_a($this->result_metadata, '\MySQLi_Result'))
  802.         {
  803.             call_user_func_array(array($this->statement_object, 'bind_result'), $this->references_result_row);
  804.         }
  805.     }
  806.    
  807.     /**
  808.      * Sets and binds parameters for the next execution.
  809.      *
  810.      * Will throw RuntimeException if the parameter array count doesn't
  811.      * match the parameter/placeholder count.
  812.      *
  813.      * Will throw RuntimeException if the parameter index doesn't contain
  814.      * all placeholders as its indexes.
  815.      *
  816.      * @throws RuntimeException
  817.      * @see execute()
  818.      * @param array $params Complete parameter array, indexed with placeholders.
  819.      *
  820.      */
  821.     protected function setAndBindParameters(array $params)
  822.     {
  823.         // Ignore method call if we don't have parameters to process
  824.         if ($this->statement_object->param_count <= 0)
  825.         {
  826.             return;
  827.         }
  828.        
  829.         $user_param_count = count($params);
  830.         $param_type_string = '';
  831.        
  832.         if ($this->statement_object->param_count != $user_param_count)
  833.         {
  834.             throw new \RuntimeException("StatementWrapper error when setting and binding parameters. Argument count ({$user_param_count}) doesn't match needed parameter count ({$this->statement_object->param_count}).");
  835.         }
  836.        
  837.         foreach ($this->params as $placeholder => $param)
  838.         {
  839.             if (!isset($params[$placeholder]))
  840.             {
  841.                 throw new \RuntimeException("StatementWrapper error when setting and binding parameters. Required parameter '{$placeholder}' is not defined when trying to set parameter.");
  842.             }
  843.            
  844.             $this->params[$placeholder] = $params[$placeholder];
  845.         }
  846.        
  847.         $this->createParamTypeString();
  848.         $this->bindParam();
  849.     }
  850.    
  851.     /**
  852.      * Fills parameter types string to the $references_param property.
  853.      *
  854.      * MySQLi_STMT::bind_param() requires us to specify parameter types
  855.      * when binding. Allowed parameter types are (as per 5.3.6):
  856.      *
  857.      * <code>
  858.      * i - integer
  859.      * d - double
  860.      * s - string
  861.      * b - blob (will be sent in packets)
  862.      * </code>
  863.      *
  864.      * This method detects if the parameter is integer or float (double)
  865.      * and defaults to string. To mark a parameter as blob, use class
  866.      * method markParamAsBlob().
  867.      *
  868.      * @see $references_params
  869.      * @see setAndBindParameters()
  870.      * @see markParamAsBlob()
  871.      *
  872.      */
  873.     protected function createParamTypeString()
  874.     {
  875.         $this->references_params['types'] = '';
  876.        
  877.         foreach ($this->params as $placeholder => $param)
  878.         {
  879.             if (in_array($placeholder, $this->blob_params))
  880.             {
  881.                 $this->references_params['types'] .= 'b';
  882.             }
  883.             else if (is_integer($param))
  884.             {
  885.                 $this->references_params['types'] .= 'i';
  886.             }
  887.             else if (is_float($param))
  888.             {
  889.                 $this->references_params['types'] .= 'd';
  890.             }
  891.             else
  892.             {
  893.                 $this->references_params['types'] .= 's';
  894.             }
  895.         }
  896.     }
  897.    
  898.     /**
  899.      * Binds parameter references array using MySQLi_STMT::bind_param().
  900.      *
  901.      * This method is called each time the user provides new arguments.
  902.      * Assumes that parameter types string has already been generated.
  903.      *
  904.      * @see $references_params
  905.      * @see createParameterVariablesAndReferences()
  906.      *
  907.      */
  908.     protected function bindParam()
  909.     {
  910.         call_user_func_array(array($this->statement_object, 'bind_param'), $this->references_params);
  911.     }
  912. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement