Duplicate entries in MySQL after performing a check (PHP) - php

Lately, I have been getting some strange duplicate entries in my MySQL database. Entries in this table are inserted during a PUT request to a PHP page. The table contains 3 fields with no external references:
manga_id (primary key; auto increment) - bigint(20);
name - varchar(255);
manga_cid - varchar(255).
The PHP code is the following:
class MangaHandler {
private function getMangaFromName($name) {
$id = $this->generateId($name);
$mangas = new Query("SELECT * FROM tbl_manga WHERE manga_cid = '" . $this->conn->escapeString($id) . "'", $this->conn);
if(!$mangas || $mangas->hasError()) {
logError("getMangaFromName($name): " . $this->conn->getError());
return null;
}
if($mangas->moveNext()) {
return $mangas->getRow();
}
return null;
}
private function addManga($name) {
$manga_row = null;
$error = false;
$cid = $this->generateId($name);
$sql = sprintf("INSERT INTO tbl_manga(name, manga_cid) VALUES ('%s', '%s')", $this->conn->escapeString($name), $this->conn->escapeString($cid));
if(!$this->conn->execute($sql))
$error = true;
// some more code ...
if($error) {
logError("addManga($name): " . $this->conn->getError());
}
return $manga_row;
}
public function addMangaSourceAndFollow($name, $url, $source_id, $user_id, $stick_source = false, $stick_lang = 'English') {
// validate url
$manga = $this->getMangaFromUrl($url, $source_id);
if(!$manga) {
$manga = $this->getMangaFromName($name);
if(!$manga) $manga = $this->addManga($name);
// ...
}
// ...
return true;
}
}
class MangaRestService extends CommonRestService
{
public function performPut($url, $arguments, $accept, $raw) {
header('Content-type: application/json');
header("Cache-Control: no-cache, must-revalidate");
$json = json_decode($raw, true);
// some input validation and auth
$ms = new MangaHandler();
try {
$ret = $ms->addMangaSourceAndFollow(null, $json['url'], $source['source_id'], $user['user_id'], $enforce == 1);
// throw exception if some ret is invalid
// return proper json response
} catch(Exception $e) {
$conn->rollback();
logError("MangaRestService.performPut($url, [" . implode("; ", $arguments) . "], $accept, $raw): " . $e->getMessage());
echo RestResponse::getSomeErrorResponse()->toJSON();
}
}
}
$serv = new MangaRestService();
// performs some checks and invokes the appropriate GET, POST, PUT or DELETE method
// in this case it's the performPut method above
$serv->handleRawRequest();
The manga name gets filtered (only alphanumeric chars, underscore and some more characters are allowed) and becomes the manga_cid (which should be unique in the table).
The code basically checks if a manga with a specific manga_cid exists. Only if it doesn't, a new entry is inserted. I have tested the code and it works, however, after deploying the app., I have been getting entries with duplicate manga_cid (sometimes more than 2). This is a rare occurrence, as most entries remain unique. Also, I have no errors in my logs.
Could it be that for some reason, multiple HTTP PUT requests are being executed concurrently and, since there is no synchronization, INSERT gets called multiple times?
I find it very unlikely, specially because the app only lets you press the button that performs this request once before it disappears.
I have tried using MySQL transactions but it didn't solve the problem. I know that setting the field as unique will probably allow me to avoid these duplicate entries, however I would have to perform some heavy maintenance on the database in order to remove the duplicate entries first. Although this table has a simple structure, the manga_id is referenced in several other tables.
Also, I am curious as to why this is happening :-)
Just in case, here's the Query class:
class Query extends QueryBase
{
function Query($query, &$conn)
{
$this->recordset = array();
$this->has_error=0;
$regs = mysqli_query($conn->getConnection(), $query);
if(!$regs)
{
$this->has_error=1;
return;
}
$index = 0;
$this->current_index=-1;
while(($row = mysqli_fetch_array($regs, MYSQL_ASSOC)))
{
$this->recordset[$index]=$row;
$index++;
}
mysqli_free_result($regs);
}
public function moveNext()
{
if($this->current_index<(sizeof($this->recordset)-1))
{
$this->current_index++;
return 1;
}
else
return 0;
}
public function moveBack()
{
if($this->current_index>=1)
{
$this->current_index--;
return 1;
}
else
return 0;
}
public function recordCount()
{
return sizeof($this->recordset);
}
public function get($field)
{
return $this->recordset[$this->current_index][$field];
}
public function getRow()
{
return $this->recordset[$this->current_index];
}
public function hasError()
{
return $this->has_error;
}
}
Thank you for your help.

It sounds as though you are executing that block of code more than once, can you post the rest of the code? The code that deals with the 'put'?

As a general rule, if the process for checking for the existence of a value and insertion of a value are separate, there can be double entries due to someone accidentally clicking the submit button more than once (for example - or other action? image click...)
It's often a good practice to embed a duplicate submission check so that double submissions are avoided.

I'm not sure if this will fix it for you, but it would be better to return "false;" instead of returning "null" upon failure from your getManageFromName() method.
Another thing to check would your hasErrors() method; it might be returning errors for some reason sometimes?
You might be better off to write it like this:
private function getMangaFromName($name) {
$id = $this->generateId($name);
$mangas = new Query("SELECT * FROM tbl_manga WHERE manga_cid = '" . $this->conn->escapeString($id) . "'", $this->conn);
if($mangas->moveNext()) {
return $mangas->getRow();
} else {
if(!$mangas || $mangas->hasError()) {
logError("getMangaFromName($name): " . $this->conn->getError());
}
return null;
}
}
// Then check it like this:
$manga = $this->getMangaFromName($name);
if ( empty($manga) ) {
$manga = $this->addManga($name);
}

Related

Can't set error message in Code Igniter 3 when using callback function

I am writing a method that uses POST variables posted by AJAX to add a user to a certain course in the database, but I can't get the callback to work correctly:
public function enroll()
{
$package = array();
$this->load->library('form_validation');
$this->form_validation->set_rules('course', 'Vak', 'required|callback_not_enrolled');
$fields = array("course");
if ($this->form_validation->run($this) === FALSE) {
$errors = array();
$success = array();
foreach ($fields as $field) {
$error = form_error($field);
if ($error !== "") {
$errors[$field] = $error;
} else {
$success[$field] = True;
}
}
$package["field_errors"] = $errors;
$package["field_success"] = $success;
$package["success"] = False;
} else {
$package["database"] = $this->course_model->enroll_user($this->data["user"], $this->input->post("course"));
$package["success"] = True;
}
echo json_encode($package);
}
I wrote the callback not_enrolled to check if the user is not already enrolled to the database. Note that I can't use is_unique because I have to test the combined uniqueness of two fields (so just one or two separate ones don't do the trick) and the id of the user is not included in the form (because it's part of the Code Igniter session).
The callback function:
public function _not_enrolled($course)
{
$exists = ($this->user->is_enrolled($course, $this->data["user_id"]) != False);
if ($exists != False) {
$this->form_validation->set_message("not_enrolled", "Already enrolled");
return False;
} else {
return True;
}
}
And finally the method is_enrolled from the model:
public function is_enrolled($course, $user=False) {
if($user==False){
$user = $this->data["user_id"];
}
$this->db->select()->from("course_participant")->where("user_id", $user)->where("course_id", $course);
$query = $this->db->get();
return($query->num_rows()>0);
}
Through a call to var_dump($this->_not_enrolled($existing_course_id)); I know that both the callback function and the method from the model work, as it correctly returned true.
When I var_dump the $package array or validation_errors() I don't get any validation errors except that it says Unable to access an error message corresponding to your field name Vak(not_enrolled).
I tried removing the initial _ from the function name but that gives me a Server Status 500 error.
I have another setup exactly like this, albeit other database calls, with a callback using the same syntax. This method works perfectly.

Variable to another php file

I have a php file(register.php) with a public function register($data) where errors are validated.Then errors are counted and if no errors are found, validation is passed.
register.php:
class ARegister {
public function register($data) {
$user = $data['userData'];
//validate provided data
$errors = $this->validateUser($data);
if(count($errors) == 0) {
//first validation
}
}
public function validateUser($data, $botProtection = true) {
$id = $data['fieldId'];
$user = $data['userData'];
$errors = array();
$validator = new AValidator();
if( $validator->isEmpty($user['password']) )
$errors[] = array(
"id" => $id['password'],
"msg" => Lang::get('password_required')
);
return $errors;
}
The problem is, that I need to get this confirmation of validated data to my other php file (othervalidation.php) where I've made another validation:
othervalidation.php:
<?php
require 'register.php';
if ( !empty($action) ) {
switch ( $action ) {
case 'process_payment':
try {
$instance = new ARegister();
if($instance->validateUser($data, $errors)) {
throw new Exception('Validation error');
}
} catch (Exception $e) {
$status = false;
$message = $e->getMessage();
}
}
How can I send the result of $errors variable to my other validation (othervalidation.php)?
I looked at your new code design and here's the new problems I found.
First, in your register function, you use the errors variable as an integer while your validate function returns an array. You got two possibilities here.
You can change your register method to check out if your error array is empty like this:
if(empty($errors)) {
//first validation
}
Count is also valid, but I still prefer empty since it's syntactically clearer. Furthermore, the count function returns 1 if the parameter is not an array or a countable object or 0 if the parameter is NULL. As I said, it is a functional solution in your current case but, in some other contexts, it might cause you unexpected results.
Here in your method declaration, I see that you are expecting a boolean (botProtection).
public function validateUser($data, $botProtection = true) {
But you are supplying an errors parameter
if($instance->validateUser($data, $errors)) {
You don't provide me the declaration of the errors variable, but it is probably not matching the bot protection parameter your function is expecting. PHP is using lose typing, it is useful but, once again, you got to be careful for bugs hard to find. For public function, you should always make sure a way or another that the supplied parameter won't lead to code crash.
In your code, the data parameter seems to be an array. You can use parameter hinting to force the use of array like this:
public function register(array $data) {
public function validateUser(array $data, $botProtection = true) {
And even specific class (as if you where using "instance of" in a condition)
public function register(MyDataClass $data) {
public function validateUser(MyDataClass $data, $botProtection = true) {
Also, you're not even using the botProtection parameter in your validateUser method.
On the same function call:
if($instance->validateUser($data, $errors)) {
you are expecting a Boolean (true or false), but the method returns an array. If you want to use the code the way it is currently designed, you must use it like this
if(!empty($instance->validateUser($data, $errors)) {
Here, I'm not so sure it is necessary to use exception. Ain't it be easier to design your code like this?
if(!empty($instance->validateUser($data, $errors)) {
$message = 'Validation error';
}
In your validate function, is the "isEmpty" function also validating if the client provided a password?
If that's the case you could validate it like this:
if(!in_array($user['password']) or empty($user['password']))
With those corrections, your code should be functional.
Here's a sample of how I would had design your code (considering the code sample provided):
class ARegister {
public function register($data) {
$user = $data['userData']; //don't declare it here, all the user validations must be done in validateUser($data, &$errors)
$errors = array();
if($this->validateUser($data, $errors)) {
//first validation
}
}
/**
* Note: If you are not returing more than one error at the time, $errors should be a string instead of an array.
*/
public function validateUser($data, array &$errors) {
$isValid = false;
if (in_array($data['fieldId']) and in_array($data['fieldId']['password']) and in_array($data['userData'])){
if(!in_array($data['userData']['password']) or empty($data['userData']['password'])){
$errors[$data['fieldId']['password']] = Lang::get('password_required');
}
else{
$isValid = true;
}
}
else{
//an invalid data array had been provided
}
return $isValid;
}
For the next part, if the code is executed directly in the view and you are a beginner, create a procedural external controller file (all functions will be public...). If you are a professional, you MUST create a class to encapsulate the treatment.
You must not do treatment directly in the view. The view is a dumb placeholder for data presentation and collecting client's input. The sole action it must do is display the data sent by the controller and send back the client's input to the controller.
The treatment on data is the controller responsibility.
if (!empty($action) ) {
$errors =array();
switch ( $action ) {
case 'process_payment':
$instance = new ARegister();
if($instance->validateUser($data, $errors)) {
//the user is valid, do the treatment
}
else
PageManager::dispayError($errors);
}
unset($instance);
}
}
Here's an example how you can centralize your error display
/**
* Can be more complexe than that, but I'm at my father's home at four hundred kms away from Montreal right now..
*/
public static function dispayError($errors, $size = 4){
if (is_numeric($size)){
if ($size < 0){
$size = 1;
}
elseif($size > 5){
$size = 5;
}
}
else{
$size = 4;
}
if (is_scalar($errors)){
echo '<h' . $size . 'class="ERROR_MESSAGE">' . $errors . '</h' . $size . '><br>';
}
elseif (is_array($errors)){
foreach ($errors as $error){
if (is_scalar($error)){
echo '<h' . $size . 'class="ERROR_MESSAGE">' . $error . '</h' . $size . '><br>';
}
}
}
}
Of course, you can also support many kind of message:
public static function dispayError($errors, $size = 4){
self::displayMessage("ERROR_MESSAGE", $errors, $size=4);
}
private static displayMessage($class, $messages, $size=4)
Well, took me two hours to write that. I hope you have now enough material to build an efficient, reusable and, no less important, safe code design.
Good success,
Jonathan Parent-Lévesque from Montreal
You can try something like this:
class ARegister {
private $error = 0;
public function register($data) {
if (!$this->validateUser($data)){
$this->error++;
}
}
public function getErrorCount(){
return $this->error;
}
public resetErrorCount(){
$this->error = 0;
}
Or pass the error by reference:
public function register(&$error, $data) {
if (!$this->validateUser($data)){
$error++;
}
}
Personally, I would do all the validation in the same method (in the class for encapsulation), use an error message parameter (passed by reference) to return why the validation failed and use the return statement to return true or false.
class MyClass{
public function validation(&$errorMessage, $firstParameter, $secondParameter){
$success = false;
if (!$this->firstValidation($firstParameter)){
$errorMessage = "this is not working pal.";
}
elseif (!this->secondeValidation($firstParameter)){
$errorMessage = "Still not working buddy...";
}
else{
$success = true;
}
return $success;
}
private function firstValidation($firstParameter){
$success = false;
return $success;
}
private function secondeValidation($secondParameter){
$success = false;
return $success;
}
}
In your other file:
<?php
$instance = new MyClass();
$errorMessage = "";
if ($instance->validation($errorMessage, $firstParameter, $secondParameter)){
echo "Woot, it's working!!!";
}
else{
echo $errorMessage;
}
?>
Is one of these code solutions fit your needs?
Jonathan Parent-Lévesque from Montreal

Return unique id when use INSERT and SELECT in one moment

i am writing a program using mvc , and because the program i am writing is for admitting bank transactions, the resaults this program gives me is very important to me. and i need this to work without any logical errors and everything should workout with needed focus and giving me the exact thing i expect it to give. the thing that i want it to do for me is that some information will be sent out to the program with web service (like price, info, etc...) and then using a controller it checks the information and calls for the module, and sends the information to it. the it puts the module information to a stdclass and then using the PDO module it creates a transaction. and while the transaction is at work an insert command is sent for the database, and then we have a SELECT command and the program works well even after the insert command but while SELECT is at work , we have repetitive IDs which is the problem here:
my code:
//WSDL Creattor And Class
<?php
ini_set("soap.wsdl_cache_enabled", 1);
set_time_limit(300);
class wsdl extends Controller
{
public function index()
{
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') {
$servidorSoap = new SoapServer('http://' . $_SERVER['SERVER_NAME'] . '/wsdl');
$servidorSoap->setClass('wsdl');
$servidorSoap->handle();
}
}
public function request($pin = null, $amount = 1, $callback = null, $orderid = null, $desc = null)
{
if(empty($callback))
return -10; // call back is empty.
if (empty($pin) && $result->pin != $pin)
return -1; // pin invalid
$this->Model("payment");
$au = $this->mod->Request($result, $amount, $callback, $orderid, $desc);
return $au;
}
}
payment model:
<?php
class Payment_mod extends Model
{
public function Request($gateway, $amount, $callback, $orderid, $description)
{
// using pdo module
$this->DB->CONNECT->beginTransaction();
try {
$trans = new stdClass;
$trans->gateway_id = $gateway->id;
$trans->au = str_replace('.', '_temp_', microtime(true));
$trans->price = $amount;
$trans->order_id = $orderid;
$trans->ip = $_SERVER['REMOTE_ADDR'];
$trans->time = time();
$trans->call_back = $callback;
$trans->description = $description;
$this->DB->Insert("trans", $trans); // insert into table trans …
$id = $this->DB->Fetch("trans", "`gateway_id` =" . $trans->gateway_id . " AND `price`=".$trans->price." AND time = " . $trans->time)->id; // select id from trans where …
$u = new stdClass;
$u->t_id = $id;
$this->DB->Insert("test",$u); // insert into test . it for test table for check result
$this->DB->CONNECT->commit();
} catch (Exception $e) {
$this->DB->CONNECT->rollBack();
return -9; // return unknow error.
}
return $id;
}
}
for understanding where the problem exactly is and how many times each ID repeats itself, i added a table and i got the amount of return data of SELECT and put it into the database. and when i hold the f5 key for some seconds it shows that each ID repeats nearly 9 times.
screenshot:
http://uploads.im/biWEn.jpg
note: this picture shows the amount of repeat of IDs.
where is the problem? i need this to repeat a specific ID for each call.
i did the exact thing in YII framework and i had no problem there and the progam worked exactly as i needed it to. i'd be glad if you help me.

Code doesn't make sense -- two conditionals within PHP try

I have inherited an application that is not doing what it's supposed to do. I have isolated the problem to the database not being properly attached. The programmer wrote this function that seemingly is suppose to evaluate whether the database is attached, calling the "attachPaymentDatabase()" function to attach it if it's not.
function attachPaymentDatabaseIfNotDoneAlready()
{
global $db;
global $hasPaymentDatabaseAttached;
// Determine if we have attached the payment tables, and if not, add them.
$hasPaymentDatabaseAttached = false;
try {
// this new way should work the best-- looking for PAY.
$alldb = queryall($db, "PRAGMA database_list;");
for ($i = 0; $i < count($alldb); $i++)
{
$alldb[$i] = array_change_key_case($alldb[$i], CASE_LOWER);
if (strtolower($alldb[$i]['name']) == 'pay')
{
debugEmail("condition 1 worked.");
$hasPaymentDatabaseAttached = true;
break;
}
}
// if its name changed this will also work
if (!$hasPaymentDatabaseAttached)
{
$r = #$db->querySingle("SELECT * FROM PAY_PARAMETER;");
$hasPaymentDatabaseAttached = true;
debugEmail("condition 2 worked.");
}
}
catch(Exception $e)
{
}
if (!$hasPaymentDatabaseAttached)
{
debugEmail("nothing worked.");
attachPaymentDatabase();
}
}
I have written a debugEmail() function that emails me a defined message with a timestamp as used above. When executing the code from the application, I can see that "condition 2 worked." is being called one second before "nothing worked.".
I don't understand how this can be. If debugEmail("condition 2 worked."); is executing, then so should too $hasPaymentDatabaseAttached = true; in which case this should not execute:
if (!$hasPaymentDatabaseAttached)
{
debugEmail("nothing worked.");
attachPaymentDatabase();
}
But it clearly is.
What is going on here?!?!?!?
No it shouldn't, because $hasPaymentDatabaseAttached is set to true in the first condition. In still nonsense at all, but it works as described.

Custom/Manual PHP function to retrieve category hierarchy from wordpress tables?

i am looking for a function to fetch wordpress category hierarchy from wordpress tables (wp_terms, wp_term_relationships, wp_term_taxonomy) WITHOUT using wordPress functions / templates.
i am basically looking to fetch categories (parent/child) relationship and then I want to insert them into my own CMS table. For this i must know "What Category Comes under What? (with all the sub-sub (multiple) directories)"
So far I made this:
$sql="SELECT a.term_id,a.description,a.parent,a.count,b.name,b.slug
FROM wp_term_taxonomy a INNER JOIN wp_terms b WHERE a.term_id=b.term_id
AND a.taxonomy='category';
";
$result = mysql_query($sql);
while ($row = mysql_fetch_assoc($result)) {
echo($row['name'].'<br>');
}
exit;
This function displays all the categories but NOT the hierarchy and i am NOT able to get the Child Parent thing..
Can anyone help me with this please?
Regards
It's not that hard. First I'd wrap that recursive stuff by letting it behave like a RecursiveIterator:
class RecursiveCategoryIterator implements RecursiveIterator {
const ID_FIELD = 'term_id';
const PARENT_FIELD = 'parent';
private $_data;
private $_root;
private $_position = 0;
public function __construct(array $data, $root_id = 0) {
$this->_data = $data;
$this->_root = $root_id;
}
public function valid() {
return isset($this->_data[$this->_root][$this->_position]);
}
public function hasChildren() {
$subid = $this->_data[$this->_root][$this->_position][self::ID_FIELD];
return isset($this->_data[$subid])
&& is_array($this->_data[$subid]);
}
public function next() {
$this->_position++;
}
public function current() {
return $this->_data[$this->_root][$this->_position];
}
public function getChildren() {
return new self($this->_data,
$this->_data[$this->_root][$this->_position][self::ID_FIELD]);
}
public function rewind() {
$this->_position = 0;
}
public function key() {
return $this->_position;
}
public static function createFromResult($result) {
$menu_array = array();
while($row = mysql_fetch_assoc($result)) {
$menu_array[$row[self::PARENT_FIELD]][] = $row;
}
return new self($menu_array);
}
}
Now why would I do that? First, because you can re-use id for displaying the tree, or do other stuff with it like import it in your own table. Second, if you have to test your code, you can just put in some other RecursiveIterator as a mock (for example a RecursiveArrayIterator).
Now the second part, the actual import of the word-press data:
// your original query
$sql="SELECT a.term_id,a.description,a.parent,a.count,b.name,b.slug
FROM wp_term_taxonomy a INNER JOIN wp_terms b WHERE a.term_id=b.term_id
AND a.taxonomy='category';
";
$result = mysql_query($sql, $dbh);
// always test for failure
if($result === false) {
die("query failed: ". mysql_error());
}
// create the iterator from the result set
$wpterms = RecursiveCategoryIterator::createFromResult($result);
// and import it.
insert_it($wpterms, 0);
// the function which does all the dirty work.
function insert_it($iterator, $parent_id = 0) {
foreach($iterator as $row) {
// insert the row, just edit the query, and don't forget
// to escape the values. if you have an insert function,
// use it by all means
$qry = 'INSERT INTO my_table (myparent, myname, ...)'
. ' VALUES (\'' . mysql_real_escape_string($parent_id)
. '\', \'' . mysql_real_escape_string($row['name']) . '\', ....)';
$status = mysql_query($qry);
if($status === false) {
// insert failed - rollback and abort
die("hard: " . mysql_error());
}
// you need to pass the id of the new row
// so the "child rows" have their respective parent
$cid = mysql_insert_id();
// insert the children too
if($iterator->hasChildren()) {
insert_it($iterator->getChildren(), $cid);
}
}
}
If you want to migrate your data to a different (and even custom-built) CMS, you should use Export, which will generate a WXR (XML) file that is very easy to parse and import into third-party systems. It includes all post types, taxonomies, meta data, attachments and everything else.
Working directly with the database is a pain, but if you will, it'll be easier to work on the WordPress side write the data into your other tables, rather than trying to read the data from somewhere else.

Categories