I'm developing a code that's supposed to go through all records in a database and run 'save()' on each of them.
The thing is, it's giving me a 'Fatal error: Allowed memory size exhausted' error (see below)
current memory usage: 19592272
current memory usage: 20944968
current memory usage: 22361824
current memory usage: 23649968
.
.
.
current memory usage: 274071160
current memory usage: 275354880
Fatal error: Allowed memory size of 276824064 bytes exhausted (tried to allocate 71 bytes) in/home/lestest/public_html/garratt/phproad/modules/phpr/classes/phpr_extensible.php on line 94
I read this was an issue with any PHP version that's below 5.3 (I'm using PHP 5.3.2). I follow advices from articles like http://paul-m-jones.com/archives/262#comment-940 and https://bugs.php.net/bug.php?id=48781I, and made sure to destruct() and unset() the object after each loop to free the memory, but I don't know why it's still keeping memory. Can anyone point out what should be changed with the code?
Below is my code:
<?php
class ProductSave_Products extends Backend_Controller {
public $implement = 'Db_FormBehavior, Db_ListBehavior';
public $form_model_class = 'ProductSave_Model';
public function index()
{
$this->app_module_name = 'Products';
$this->app_page_title = 'Save';
$obj = new ProductSave_Model();
$ids = Db_DbHelper::queryArray('select id from shop_product');
foreach($ids as $row)
{
$product = Shop_Product::create()->find($row['id']);
$product->save();
$product->__destruct();
unset($product);
print "current memory usage: ". memory_get_usage() . '<br/><br/>';
}
}
}
?>
Here is my Db_Helper class code:
<?php
class Db_DbHelper
{
protected static $driver = false;
public static function listTables()
{
return Db_Sql::create()->fetchCol('show tables');
}
public static function tableExists($tableName)
{
$tables = self::listTables();
return in_array($tableName, $tables);
}
public static function executeSqlScript($filePath, $separator = ';')
{
$fileContents = file_get_contents($filePath);
$fileContents = str_replace( "\r\n", "\n", $fileContents );
$statements = explode( $separator."\n", $fileContents );
$sql = Db_Sql::create();
foreach ( $statements as $statement )
{
if ( strlen(trim($statement)) )
$sql->execute($statement);
}
}
public static function scalar($sql, $bind = array())
{
return Db_Sql::create()->fetchOne($sql, $bind);
}
public static function scalarArray($sql, $bind = array())
{
$values = self::queryArray($sql, $bind);
$result = array();
foreach ($values as $value)
{
$keys = array_keys($value);
if ($keys)
$result[] = $value[$keys[0]];
}
return $result;
}
public static function query($sql, $bind = array())
{
$obj = Db_Sql::create();
return $obj->query($obj->prepare($sql, $bind));
}
public static function fetch_next($resource)
{
return self::driver()->fetch($resource);
}
public static function free_result($resource)
{
self::driver()->free_query_result($resource);
}
public static function queryArray($sql, $bind = array())
{
return Db_Sql::create()->fetchAll($sql, $bind);
}
public static function objectArray($sql, $bind = array())
{
$recordSet = self::queryArray($sql, $bind);
$result = array();
foreach ($recordSet as $record)
$result[] = (object)$record;
return $result;
}
public static function object($sql, $bind = array())
{
$result = self::objectArray($sql, $bind);
if (!count($result))
return null;
return $result[0];
}
public static function getTableStruct( $tableName )
{
$sql = Db_Sql::create();
$result = $sql->query($sql->prepare("SHOW CREATE TABLE `$tableName`"));
return $sql->driver()->fetch($result, 1);
}
public static function getTableDump( $tableName, $fp = null, $separator = ';' )
{
$sql = Db_Sql::create();
$qr = $sql->query("SELECT * FROM `$tableName`");
$result = null;
$columnNames = null;
while ($row = $sql->driver()->fetch($qr))
{
if ( $columnNames === null )
$columnNames = '`'.implode( '`,`', array_keys($row) ).'`';
if (!$fp)
{
$result .= "INSERT INTO `$tableName`(".$columnNames.") VALUES (";
$result .= $sql->quote( array_values($row) );
$result .= ")".$separator."\n";
} else
{
fwrite($fp, "INSERT INTO `$tableName`(".$columnNames.") VALUES (");
fwrite($fp, $sql->quote( array_values($row) ));
fwrite($fp, ")".$separator."\n");
}
}
return $result;
}
public static function createDbDump($path, $options = array())
{
#set_time_limit(600);
$tables_to_ignore = array_key_exists('ignore', $options) ? $options['ignore'] : array();
$separator = array_key_exists('separator', $options) ? $options['separator'] : ';';
$fp = #fopen($path, "w");
if (!$fp)
throw new Phpr_SystemException('Error opening file for writing: '.$path);
$sql = Db_Sql::create();
try
{
fwrite($fp, "SET NAMES utf8".$separator."\n\n");
$tables = self::listTables();
foreach ($tables as $index=>$table)
{
if (in_array($table, $tables_to_ignore))
continue;
fwrite($fp, '# TABLE '.$table."\n#\n");
fwrite($fp, 'DROP TABLE IF EXISTS `'.$table."`".$separator."\n");
fwrite($fp, self::getTableStruct($table).$separator."\n\n" );
self::getTableDump($table, $fp, $separator);
$sql->driver()->reconnect();
}
#fclose($fp);
#chmod($path, Phpr_Files::getFilePermissions());
}
catch (Exception $ex)
{
#fclose($fp);
throw $ex;
}
}
/**
* Generates an unique column value
* #param Db_ActiveRecord $model A model to generate value for
* #param string $column_name A name of a column
* #param string $base_value A base value of the column. The unique value will be generated
* by appending the 'copy_1', 'copy_N' string to the base value.
* #param bool $case_sensitive Specifies whether function should perform a case-sensitive search
* #return string
*/
public static function getUniqueColumnValue($model, $column_name, $base_value, $case_sensitive = false)
{
$base_value = trim($base_value);
$base_value = preg_replace('/_copy_[0-9]+$/', '', $base_value);
$column_value = $base_value;
$counter = 1;
$table_name = $model->table_name;
$query = $case_sensitive ?
"select count(*) from $table_name where $column_name=:test_value" :
"select count(*) from $table_name where lower($column_name)=lower(:test_value)";
while (self::scalar("select count(*) from $table_name where $column_name=:test_value", array(
'test_value'=>$column_value
)))
{
$column_value = $base_value.'_copy_'.$counter;
$counter++;
}
return $column_value;
}
/**
* Creates a SQL query string for searching specified fields for specified words or phrases
* #param string $query Search query
* #param array|array $fields A list of fields to search in. A single field can be specified as a string
* #param int $min_word_length Allows to ignore words with length less than the specified
* #return string Returns a string
*/
public static function formatSearchQuery($query, $fields, $min_word_length = null)
{
if (!is_array($fields))
$fields = array($fields);
$words = Core_String::split_to_words($query);
$word_queries = array();
foreach ($words as $word)
{
if (!strlen($word))
continue;
if ($min_word_length && mb_strlen($word) < $min_word_length)
continue;
$word = trim(mb_strtolower($word));
$word_queries[] = '%1$s like \'%2$s'.self::driver()->escape($word).'%2$s\'';
}
$field_queries = array();
foreach ($fields as $field)
{
if ($word_queries)
$field_queries[] = '('.sprintf(implode(' and ', $word_queries), $field, '%').')';
}
if (!$field_queries)
return '1=1';
return '('.implode(' or ', $field_queries).')';
}
public static function reset_driver()
{
self::$driver = false;
}
public static function driver()
{
if (!self::$driver)
{
$sql = Db_Sql::create();
return self::$driver = $sql->driver();
}
return self::$driver;
}
public static function escape($str)
{
return self::driver()->escape($str);
}
}
?>
Thank you
Related
Trying to generate a custom mysqli class / wrapper with $this->_mysqli as a mysqli instance:
// Query string generator
private function gen_query($type, $data, $table){
switch ($type) {
case 'ins':
$query = "INSERT INTO " .$table .' ';
$query .= implode_key($opr= ', ', $data);
$query .= " VALUES " . value($data);
break;
case 'select':
// yet to generate
default:
$query ='';
break;
}
return $query;
}
// Generates bind parameters
private function gen_param($data){
$_param = "'";
foreach ($data as $v) {
$_param .= $this->detect_type($v);
}
$_param .= "', ";
foreach ($data as $k=>$v) {
if($v == end($data)) {
$_param .="$$k";
continue;
}
$_param .= "$$k, ";
}
return $_param;
}
public function insert( $table, $data ){
$table = $this->_prefix . $table;
$table = $this->escape($table);
$query = $this->gen_query('ins', $data, $table);
$stmt = $this->_mysqli->prepare($query);
foreach ($data as $key => $value) {
$$key = $value;
}
$test = $this->gen_param($data);
if(!$stmt->bind_param($test)) {
echo $this->_mysqli->error;
}
if($stmt->execute()){
print 'Success!'.'<br />';
} else {
die('Error : ('. $this->_mysqli->errno .') '. $this->_mysqli->error);
}
}
So when user inputs
$data = [ 'first_name' => 'foo', 'last_name' => 'bar', 'another_field' => 'blah'];
$db->insert('t1', $data);
I get this error:
Warning: Wrong parameter count for mysqli_stmt::bind_param() in path\to\class-db.php on line 138
This is line 138: if(!$stmt->bind_param($test))
Not sure why the question was downvoted. Anyways, I got this fixed by referring: this repo.
We'll need to get rid of gen_param, use an array instead and call using a callback function to get the parameter values right and refer the values.
The code is now:
public function insert( $table, $data ){
$table = $this->_prefix . $table;
$table = $this->escape($table);
$this->esc_sql_arr($data);
$query = $this->gen_query('ins', $data, $table);
$stmt = $this->_mysqli->prepare($query);
$this->_bind_param($data);
call_user_func_array(array($stmt, 'bind_param'),$this->return_ref($this->bind_arr));
if($stmt->execute()){
echo "Success!";
}else{
die('Error : ('. $this->_mysqli->errno .') '. $this->_mysqli->error);
}
$this->reset();
}
private function _bind_param($data){
foreach($data as $key=>$value) {
$this->_bind_values($value);
}
}
private function _bind_values($value) {
$this->bind_arr[0] .= $this->detect_type($value);
array_push($this->bind_arr, $value);
}
protected function return_ref(array &$arr)
{
//Reference in the function arguments are required for HHVM to work
//https://github.com/facebook/hhvm/issues/5155
//Referenced data array is required by mysqli since PHP 5.3+
if (strnatcmp(phpversion(), '5.3') >= 0) {
$refs = array();
foreach ($arr as $key => $value) {
$refs[$key] = & $arr[$key];
}
return $refs;
}
return $arr;
}
The code is nowhere near complete, but this got me started.
In my customer repository, I've defined a function findMapAllByIds(), like so:
/**
* #apiParam $materialIdList [049c5355-6311-c251-16b7-9c923f8de2a4,0589775e-dcd3-c015-bd81-bba26df33c77,0bf7f653-c01a-d6e2-8cdc-1691c437e4eb]
*/
public function findMapAllByIds($materialIdList, $orderBy = 'createTime', $order = 'desc')
{
$ids = array();
$query = $this->createQueryBuilder('Material')
->field('_id')->in($materialIdList)
->sort($orderBy, $order)
->getQuery();
$resultList = $query->execute();
$resultMap = array();
if (!empty($resultList)) {
foreach ($resultList as $material) {
$resultMap[(string) $material->getId()] = $material;
}
}
return $resultMap;
}
Then, I get this error:
[Symfony\Component\Debug\Exception\ContextErrorException]
Notice: Array to string conversion
I'm in a situation where I want to build a code which gets $bindParam variable in this format:
$bindParams = [$type1 => $param1, $type2 => $param2, ... ]
I wanna build some code that dynamically adds that parameters to the prepared statement.
This is the code which I built so far :
$mysql = new mysqli("localhost", "root", "", "db1");
$stmt = $mysql->prepare($sql);
foreach($bindParams as $type => $data) {
$stmt->bind_param($type, $data);
}
$stmt->execute();
$result = $stmt->get_result();
// and after perhaps twiddling with the result set, but this is not the case .....
For your instance
$sql = "INSERT INTO table1 (name, age) VALUES (?,?);"
and
$bindParams = ["s" => "hello", "i" => 15]
This does not always have this structure and it can change to for example $bindParams = ["s" => "hello", "i" => 15, "d" => 22.5] and so the $sql changes respectively.
After the first time the compiler heads to $stmt->bind_param($type, $data); firefox flushes this error:
Warning: mysqli_stmt::bind_param(): Number of variables doesn't match number of parameters in prepared statement in D:\PHP\tr.php on line 23
I know PDO support that as stated here at the end of the page. but perhaps as you might expect Im not a fan of PDO so ;)
My other option is to use the eval() workarounds available in php but thats out of what I might think of.
Is there another way to do this?
I had the same problem, and found an answer much simplier:
$array_of_values = array( "Brasil", "Argentina" );
$types = "ss";
$mysqli_stmt->bind_param( $types, ...$array_of_values );
This is called "argument unpacking", and is available since PHP 5.6
http://php.net/manual/pt_BR/migration56.new-features.php
Sadly mysqli doesn't support this. Calling the function over and over again overwrites the values, so you're only binding one param when you clearly have more.
There's a couple of ways to get around this
Switch to PDO. You can make one bind per function call with that
Bind the params as one aggregate using call_user_func_array
$sqltype = '';
$sqldata = [];
foreach($bindParams as $type => $data) {
$sqltype .= $type;
$sqldata[] = &$data; // MUST be a reference
}
array_unshift($sqldata, $sqltype); // prepend the types
call_user_func_array([$stmt, 'bind_param'], $sqldata);
I use something like this to do dynamic procedure calls.
Example Call:
$mapi = new MySQLIAPI($con);
$mapi->BeginProc();
$mapi->AddParameter("user", $usern, "s");
$mapi->AddParameter("email", $email, "s");
$mapi->AddParameter("passwd", $pwd, "s");
$id = $mapi->CallProc("ij_create_user");
$id = $id[0];
if(isset($id['mysql_error']) || isset($id["error"])){
return "error";
}
return $id["id"];
Example Class:
class MySQLIAPI
{
private $con = null;
private $Variables = null;
private $values = null;
private $types = null;
private $vQu = null;
private $stmt = null;
function __construct($dbc)
{
$this->con = $dbc;
$this->Variables = [];
$this->values = [];
$this->types = [];
$this->vQu = [];
}
function BeginProc()
{
$this->stmt = $this->con->stmt_init(); // initialize statement
}
function AddParameter($key, $val, $type)
{
$this->Variables[] = "#" . $key;
$this->values[] = $val;
$this->types[] = $type;
$this->vQu[] = "?";
}
//KeyPair is v = the value, t = the type s or d
function CallProc($Proc) {
$out_var = null;
$call = "";
if(sizeof($this->values) > 0)
$call = "CALL ".$Proc."(".implode(",", (array)$this->vQu).")";
else
$call = "CALL ".$Proc."()";
if($this->stmt->prepare($call));//call stored procedure with database server session variable
{
if(sizeof($this->values) > 0) {
$params = array_merge(array(implode("", $this->types)), $this->values);
call_user_func_array(array($this->stmt, 'bind_param'), $this->refValues($params));
}
$this->stmt->execute();
$result = $this->stmt->get_result();
/* Error Checking */
$mySQLiError = mysqli_stmt_error($this->stmt);
if ($mySQLiError != "") {
$this->resetStmt();
$this->stmt->close();
$this->stmt = null;
return array('mysql_error' => $mySQLiError);
}
while ($row = $result->fetch_array(MYSQLI_ASSOC))
{
$out_var[] = $row;
}
$result->free();
while($this->stmt->more_results())
{
$this->stmt->next_result();
}
$this->resetStmt();
$this->stmt->close();
$this->stmt = null;
}
return $out_var;
}
private function refValues($arr)
{
if (strnatcmp(phpversion(), '5.3') >= 0) //Reference is required for PHP 5.3+
{
$refs = array();
foreach ($arr as $key => $value)
$refs[$key] =& $arr[$key];
return $refs;
}
return $arr;
}
private function resetStmt()
{
//Reset Params
$this->Variables = array();
$this->values = array();
$this->types = array();
$this->vQu = array();
}
}
I need to be able to echo a value from a private property in one of my classes if a method is called within the class. It's a little tricky to explain so let me demostrate and hopefully someone can fill in the blank for me :)
<?php
class test {
private $array['teachers']['classes'][23] = "John";
public function __construct($required_array) {
$this->array['teachers']['classes'][23] = "John";
$this->array['students'][444] = "Mary";
$this->echo_array($required_array);
}
public function echo_array($array) {
// Echo the value from the private $this->array;
// remembering that the array I pass can have either
// 1 - 1000 possible array values which needs to be
// appended to the search.
}
}
// Getting the teacher:
$test = new test(array('teachers','classes',23));
// Getting the student:
$test = new test(array('students',444));
?>
Is this possible?
$tmp = $this->array;
foreach ($array as $key) {
$tmp = $tmp[$key];
}
// $tmp === 'John'
return $tmp; // never echo values but only return them
An other approach to get value;
class Foo {
private $error = false,
$stack = array(
'teachers' => array(
'classes' => array(
23 => 'John',
24 => 'Jack',
)
)
);
public function getValue() {
$query = func_get_args();
$stack = $this->stack;
$result = null;
foreach ($query as $i) {
if (!isset($stack[$i])) {
$result = null;
break;
}
$stack = $stack[$i];
$result = $stack;
}
if (null !== $result) {
return $result;
}
// Optional
// trigger_error("$teacher -> $class -> $number not found `test` class", E_USER_NOTICE);
// or
$this->error = true;
}
public function isError() {
return $this->error;
}
}
$foo = new Foo();
$val = $foo->getValue('teachers', 'classes', 24); // Jack
// $val = $foo->getValue('teachers', 'classes'); // array: John, Jack
// $val = $foo->getValue('teachers', 'classes', 25); // error
if (!$foo->isError()) {
print_r($val);
} else {
print 'Value not found!';
}
Having some trouble with the following code. I've created a class to manage the DB connection, using what you see below as queryPreparedQuery and works fine when getting data for a single user, or any data that returns a single result using something like this...
include 'stuff/class_stuff.php';
function SweetStuff() {
$foo = new db_connection();
$foo->queryPreparedQuery("SELECT Bacon, Eggs, Coffee FROM Necessary_Items WHERE Available = ?",$bool);
$bar = $foo->Load();
$stuff = 'Brand of Pork is '.$bar['Bacon'].' combined with '.$bar['Eggs'].' eggs and '.$bar['Coffee'].' nectar for energy and heart failure.';
return $stuff;
}
echo SweetStuff();
Problem is, I want to build the functionality in here to allow for a MySQL query which returns multiple results. What am I missing? I know it's staring me right in the face...
class db_connection
{
private $conn;
private $stmt;
private $result;
#Build a mysql connection
public function __construct($host="HOST", $user="USER", $pass="PASS", $db="DB_NAME")
{
$this->conn = new mysqli($host, $user, $pass, $db);
if(mysqli_connect_errno())
{
echo("Database connect Error : "
. mysqli_connect_error());
}
}
#return the connected connection
public function getConnect()
{
return $this->conn;
}
#execute a prepared query without selecting
public function execPreparedQuery($query, $params_r)
{
$stmt = $this->conn->stmt_init();
if (!$stmt->prepare($query))
{
echo("Error in $statement when preparing: "
. mysqli_error($this->conn));
return 0;
}
$types = '';
$values = '';
$index = 0;
if(!is_array($params_r))
$params_r = array($params_r);
$bindParam = '$stmt->bind_param("';
foreach($params_r as $param)
{
if (is_numeric($param)) {
$types.="i";
}
elseif (is_float($param)) {
$types.="d";
}else{
$types.="s";
}
$values .= '$params_r[' . $index . '],';
$index++;
}
$values = rtrim($values, ',');
$bindParam .= $types . '", ' . $values . ');';
if (strlen($types) > 0)
{
//for debug
//if(strpos($query, "INSERT") > 0)
//var_dump($params_r);
eval($bindParam);
}
$stmt->execute();
return $stmt;
}
#execute a prepared query
public function queryPreparedQuery($query, $params_r)
{
$this->stmt = $this->execPreparedQuery($query, $params_r);
$this->stmt->store_result();
$meta = $this->stmt->result_metadata();
$bindResult = '$this->stmt->bind_result(';
while ($columnName = $meta->fetch_field()) {
$bindResult .= '$this->result["'.$columnName->name.'"],';
}
$bindResult = rtrim($bindResult, ',') . ');';
eval($bindResult);
}
#Load result
public function Load(&$result = null)
{
if (func_num_args() == 0)
{
$this->stmt->fetch();
return $this->result;
}
else
{
$res = $this->stmt->fetch();
$result = $this->result;
return $res;
}
}
#Load result
public function Execute(&$result = null)
{
if (func_num_args() == 0)
{
$this->stmt->fetch_array();
return $this->result;
}
else
{
$res = $this->stmt->fetch_array();
$result = $this->result;
return $res;
}
}
private function bindParameters(&$obj, &$bind_params_r)
{
call_user_func_array(array($obj, "bind_param"), $bind_params_r);
}
}
UPDATE
Got this to work with Patrick's help. Was able to find the following code with the help of this question, and with a few tweaks, it works beautifully. Added the following after the execute() statement in ExecPreparedQuery, returning an array at the very end instead of the single result:
# these lines of code below return multi-dimentional/ nested array, similar to mysqli::fetch_all()
$stmt->store_result();
$variables = array();
$data = array();
$meta = $stmt->result_metadata();
while($field = $meta->fetch_field())
$variables[] = &$data[$field->name]; // pass by reference
call_user_func_array(array($stmt, 'bind_result'), $variables);
$i=0;
while($stmt->fetch())
{
$array[$i] = array();
foreach($data as $k=>$v)
$array[$i][$k] = $v;
$i++;
}
# close statement
$stmt->close();
return $array;
As a result of the altered code, I changed the call to interpret multidimensional array data rather than a single result, of course. Thanks again!
In your Execute function you are calling $this->stmt>fetch_array().
That function only returns an array of a single row of the result set.
You probably want:
$this->stmt->fetch_all()
Update
To retrieve the entire result set from a prepared statement:
$this->stmt->store_result()