Should I refactor this code? - php

The code is for a view debate page. The code is supposed to determine whether or not to show an add reply form to the viewing user.
If the user is logged in, and the user is not the creator of the debate, then check if the user already replied to the debate.
If the user did not already reply to the debate then show the form...
Otherwise, Check If the user wants to edit their already existing reply by looking in the url for the reply id
If any of these tests dont pass, Then I save the reason as an int and pass that to a switch statement in the view.
The logic seems easy enough, but my code seems a little sloppy.
Here's the code.. (using Kohana V2.3.4)
public function view($id = 0)
{
$debate = ORM::factory('debate')->with('user')->with('category')->find($id);
if ($debate->loaded == FALSE)
{
url::redirect();
}
// series of tests to show an add reply form
if ($this->logged_in)
{
// is the viewer the creator?
if ($this->user->id != $debate->user->id)
{
// has the user already replied?
if (ORM::factory('reply')
->where(array('debate_id' => $id, 'user_id' => $this->user->id))
->count_all() == 0)
{
$form = $errors = array
(
'body' => '',
'choice_id' => '',
'add' => ''
);
if ($post = $this->input->post())
{
$reply = ORM::factory('reply');
// validate and insert the reply
if ($reply->add($post, TRUE))
{
url::redirect(url::current());
}
$form = arr::overwrite($form, $post->as_array());
$errors = arr::overwrite($errors, $post->errors('reply_errors'));
}
}
// editing a reply?
else if (($rid = (int) $this->input->get('edit'))
AND ($reply = ORM::factory('reply')
->where(array('debate_id' => $id, 'user_id' => $this->user->id))
->find($rid)))
{
$form = $errors = array
(
'body' => '',
'choice_id' => '',
'add' => ''
);
// autocomplete the form
$form = arr::overwrite($form, $reply->as_array());
if ($post = $this->input->post())
{
// validate and insert the reply
if ($reply->edit($post, TRUE))
{
url::redirect(url::current());
}
$form = arr::overwrite($form, $post->as_array());
$errors = arr::overwrite($errors, $post->errors('reply_errors'));
}
}
else
{
// user already replied
$reason = 3;
}
}
else
{
// user started the debate
$reason = 2;
}
}
else
{
// user is not logged in.
$reason = 1;
}
$limits = Kohana::config('app/debate.limits');
$page = (int) $this->input->get('page', 1);
$offset = ($page > 0) ? ($page - 1) * $limits['replies'] : 0;
$replies = ORM::factory('reply')->with('user')->with('choice')->where('replies.debate_id', $id);
$this->template->title = $debate->topic;
$this->template->debate = $debate;
$this->template->body = View::factory('debate/view')
->set('debate', $debate)
->set('replies', $replies->find_all($limits['replies'], $offset))
->set('pagination', Pagination::factory(array
(
'style' => 'digg',
'items_per_page' => $limits['replies'],
'query_string' => 'page',
'auto_hide' => TRUE,
'total_items' => $total = $replies->count_last_query()
))
)
->set('total', $total);
// are we showing the add reply form?
if (isset($form, $errors))
{
$this->template->body->add_reply_form = View::factory('reply/add_reply_form')
->set('debate', $debate)
->set('form', $form)
->set('errors', $errors);
}
else
{
$this->template->body->reason = $reason;
}
}
Heres the view, theres some logic in here that determines what message to show the user.
<!-- Add Reply Form -->
<?php if (isset($add_reply_form)): ?>
<?php echo $add_reply_form; ?>
<?php else: ?>
<?php
switch ($reason)
{
case 1 :
// not logged in, show a message
$message = 'Add your ' . html::anchor('login?url=' . url::current(TRUE), '<b>vote</b>') . ' to this discussion';
break;
case 2 :
// started the debate. dont show a message for that.
$message = NULL;
break;
case 3:
// already replied, show a message
$message = 'You have already replied to this debate';
break;
default:
// unknown reason. dont show a message
$message = NULL;
break;
}
?>
<?php echo app::show_message($message, 'h2'); ?>
<?php endif; ?>
<!-- End Add Reply Form -->
Should I refactor the add reply logic into another function or something.... It all works, it just seems real sloppy.
Thanks
Edit: I took all answers into consideration. Since I wasn't adding anything new at the moment and had time to kill, I chose to refactor the code. After a little thought, a better solution popped out to me. The whole process took me about 30 minutes, and I would say it was worth it. Thanks to all for your answers

Yes, refactor. Remove the PHP and use a real language. ;)
Seriously though, do refactor - avoid nesting if statements so deeply (doing so obfuscates logic & makes testing harder) and chunk monolithic sections into separate functions/methods.

No. If you've got one more line of code to write elsewhere on this project, spend your time on that instead.
As is often the case, there will be a ton of different ways to solve the same problem your code is solving. But if you've already solved the problem then take note of what you've learnt here and move on. If this code does turn out to be a weak link later on in development, then fine; you've got proof and a concrete validation that it should be re-factored. Until then you're wasting time that could be spent pushing the project forward by re-inventing your re-invention of the wheel.

I'd say Yes. Refactor it, measure the time it takes you, then when all done assess the improvement. How much time did it take? Was it worth it? So refactor it as an experiment. And please let us know your results. Bottom line: was it worth refactoring?

avoid nested if statement :)

Yes, refactor. If you run a cyclomatic complexity analysis against this code, it would probably return a pretty high number (bad). Elaborate case/switch statements, nested if's all contribute to a higher score.
A future developer who may need to work with this codebase would potentially run a cyclomatic complexity analysis before diving in, and probably estimate that there is relatively high risk/complexity in dealing with this codebase.

Related

How to trigger an event in Laravel when nothing has been added to the database versus being alerted to when it has been updated?

I am building a simple auction solution on Laravel. The below code is some extract test code that is suppose to act as the auctioneer.
The problem is that when it's running it locks up access to the Auctionbid Postgres table and users can't bid while it's running. My testing is on localhost running a few users in auction as well as the auctioneer process all on same box/same IP<--might be making issue worse (not sure).
I've read all about the issues on SO about sleep(), downsides of polling and PHP sessions and have dug into Websockets a bit to see if I should go there. It also would also allow me to replace Pusher.
However, the issue I see is that an auction is all about timing and what events are NOT happening (if no bid in 3 seconds tell users "Fair Warning") and the Websocket implementations I've read about seem to be about what events ARE happening.
Questions:
Would a websockets implementation possibly be a solution? Every article I've looked at seems to be about the events that ARE happening. Maybe the classes on negative space are hindering my ability to see the solution :-)
Is having this all run on the same box/IP the issue? And/Or is sleep() just a bad way moving forward - as pointed out in at least 100 SO posts...
What are other possible solutions that allow me to check for what events are NOT happening?
public function RunTheAuction($auction_item_id, $auction_id)
{
set_time_limit(300);
$options = array(
'cluster' => env('PUSHER_APP_CLUSTER'),
'encrypted' => true
);
$pusher = new Pusher(
env('PUSHER_APP_KEY'),
env('PUSHER_APP_SECRET'),
env('PUSHER_APP_ID'),
$options
);
$bidIncrementResetCounter = 0;
$lastBidderId = 0;
$currentWinnerBidAmount = 0;
$lastBidderBidAmount = 0;
$count = 0;
while(true){
$auctionbid = Auctionbid::select('id', 'bid', 'bid_timestamp', 'auction_bidder_id')
->where('auction_item_id', '=', $auction_item_id)
->where('auction_id', '=', $auction_id)
->orderBy('bid', 'desc')
->orderBy('bid_timestamp', 'asc')
->first();
if ($auctionbid) { //there is a bid on the item
$currentWinnerId = $auctionbid->id;
$currentWinnerBidAmount = $auctionbid->bid;
if (($lastBidderId == $currentWinnerId) && ($lastBidderBidAmount == $currentWinnerBidAmount)) { //it's the same bidder
if ($bidIncrementResetCounter == 1) {
$message = "Fair Warning!";
$pusher->trigger('bids_channel', 'bid-warning', $message);
}
if ($bidIncrementResetCounter == 2) {
$message = "Going once... Going twice...";
$pusher->trigger('bids_channel', 'bid-warning', $message);
}
if ($bidIncrementResetCounter == 3) {
$message = "We have a winner!";
$pusher->trigger('bids_channel', 'bid-warning', $message);
break;
}
$bidIncrementResetCounter++;
} else {
$bidIncrementResetCounter = 0;
}
}
else{
$currentWinnerId = 0;
}
$lastBidderId = $currentWinnerId;
$lastBidderBidAmount = $currentWinnerBidAmount;
sleep(10); //<-----------------------------ISSUE
$count++;
if($count == 12){break;}
} //end loop
} //end function RunTheAuction

file_get_contents occasionally causes warning, but still fetches data

My script is working most of the times, but in every 8th try or so I get an error. I'll try and explain this. This is the error I get (or similar):
{"gameName":"F1 2011","gameTrailer":"http://cdn.akamai.steamstatic.com/steam/apps/81163/movie_max.webm?t=1447354814","gameId":"44360","finalPrice":1499,"genres":"Racing"}
{"gameName":"Starscape","gameTrailer":"http://cdn.akamai.steamstatic.com/steam/apps/900679/movie_max.webm?t=1447351523","gameId":"20700","finalPrice":999,"genres":"Action"}
Warning: file_get_contents(http://store.steampowered.com/api/appdetails?appids=400160): failed to open stream: HTTP request failed! in C:\xampp\htdocs\GoStrap\game.php on line 19
{"gameName":"DRAGON: A Game About a Dragon","gameTrailer":"http://cdn.akamai.steamstatic.com/steam/apps/2038811/movie_max.webm?t=1447373449","gameId":"351150","finalPrice":599,"genres":"Adventure"}
{"gameName":"Monster Mash","gameTrailer":"http://cdn.akamai.steamstatic.com/steam/apps/900919/movie_max.webm?t=1447352342","gameId":"36210","finalPrice":449,"genres":"Casual"}
I'm making an application that fetches information on a random Steam game from the Steam store. It's quite simple.
The script takes a (somewhat) random ID from a text file (working for sure)
The ID is added to the ending of an URL for the API, and uses file_get_contents to fetch the file. It then decodes json. (might be the problem somehow)
Search for my specified data. Final price & movie webm is not always there, hence the if(!isset())
Decide final price and ship back to ajax on index.php
The error code above suggests that I get the data I need in 4 cases, and an error once. I only wanna receive ONE json string and return it, and only in-case $game['gameTrailer'] and $game['final_price'] is set.
This is the php (it's not great, be kind):
<?php
//Run the script on ajax call
if(isset($_POST)) {
fetchGame();
}
function fetchGame() {
$gameFound = false;
while(!$gameFound) {
////////// ID-picker //////////
$f_contents = file("steam.txt");
$url = $f_contents[mt_rand(0, count($f_contents) - 1)];
$answer = explode('/',$url);
$gameID = $answer[4];
$trimmed = trim($gameID);
////////// Fetch game //////////
$json = file_get_contents('http://store.steampowered.com/api/appdetails?appids='.$trimmed);
$game_json = json_decode($json, true);
if(!isset($game_json[$trimmed]['data']['movies'][0]['webm']['max']) || !isset($game_json[$trimmed]['data']['price_overview']['final'])) {
continue;
}
$gameFound = true;
////////// Store variables //////////
$game['gameName'] = $game_json[$trimmed]['data']['name'];
$game['gameTrailer'] = $game_json[$trimmed]['data']['movies'][0]['webm']['max'];
$game['gameId'] = $trimmed;
$game['free'] = $game_json[$trimmed]['data']['is_free'];
$game['price'] = $game_json[$trimmed]['data']['price_overview']['final'];
$game['genres'] = $game_json[$trimmed]['data']['genres'][0]['description'];
if ($game['free'] == TRUE) {
$game['final_price'] = "Free";
} elseif($game['free'] == FALSE || $game['final_price'] != NULL) {
$game['final_price'] = $game['price'];
} else {
$game['final_price'] = "-";
}
}
////////// Return to AJAX (index.php) //////////
echo
json_encode(array(
'gameName' => $game['gameName'],
'gameTrailer' => $game['gameTrailer'],
'gameId' => $game['gameId'],
'finalPrice' => $game['final_price'],
'genres' => $game['genres'],
))
;
}
?>
Any help will be appreciated. Like, are there obvious reason as to why this is happening? Is there a significantly better way? Why is it re-iterating itself at least 4 times when it seems to have fetched that data I need? Sorry if this post is long, just trying to be detailed with a lacking php/json-vocabulary.
Kind regards, John
EDIT:
Sometimes it returns no error, just multiple objects:
{"gameName":"Prime World: Defenders","gameTrailer":"http://cdn.akamai.steamstatic.com/steam/apps/2028642/movie_max.webm?t=1447357836","gameId":"235360","finalPrice":899,"genres":"Casual"}
{"gameName":"Grand Ages: Rome","gameTrailer":"http://cdn.akamai.steamstatic.com/steam/apps/5190/movie_max.webm?t=1447351683","gameId":"23450","finalPrice":999,"genres":"Simulation"}

Form Post Data As Array Value

I'm trying to integrate an API builder to my control panel through a form or post data. I can't figure out how to put the post data as the value for the array.
I tried using print_r($_POST['VALUE']) with and without quotes.
I tried using just $_POST['VALUE'] with and without quotes.
I also tried to set $value = $_POST['VALUE'] then using $value with and without quotes but that caused an error 500.
Here is the code I am trying to use:
$res = $api->remoteCall('requestLogin', array(
'type' => 'external',
'domain' => 'print_r($_POST['domain'])',
'lang' => 'en',
'username' => 'print_r($_POST['uname'])',
'password' => 'print_r($_POST['pass'])',
'apiUrl' => '127.0.0.1',
'uploadDir' => '/web/'.print_r($_POST['domain']).'/public_html',
I apologize as I am new to PHP, but thank you in advance.
I'm not sure what other logic is being done there, how the post variables are being sent to the script your sample code is running on, or any of the other details which might point towards a more complete solution but here are some basic tips to help you troubleshoot.
The post variables should be formatted like this:
$res = $api->remoteCall('requestLogin', array(
'domain' => $_POST['domain'],
You can dump the entire post array to the screen by doing
print_r($_POST);
This should output your array to the screen so you can verify that you're receiving the post data in the code and should help you fix any typos or misnamed post variables. If the array has the key as $_POST['domainName'] and you're echoing $_POST['domain']
You're calling code (the "form or post data") should have the post fields in place and named correctly in order for them to be sent to the script
<input type="text" name="domain">
You should be performing some basic validation on your post fields before adding them to something that's going to be stored anywhere or sent off to a third-party. At the most minimal you'll want to check that there is a value being set for the essential fields (required fields) and I'd look to make sure the values are matching requirements of the API you're passing them off to.
Several things may go wrong when using api. POST values, input values, API call or connection or maybe api response. So not only at the time of implementation and coding but also when integrating api call script with the application there should be some sort of testing and error handling in place. A simple script can be like this
$error = array();
$request = array();
$request['type'] = 'external';
if (isset($_POST['domain']) && !empty($_POST['domain'])) {
$request['domain'] = $_POST['domain'];
$request['uploadDir'] = "/web/{$_POST['domain']}/public_html";
} else {
$error[] = "Domain is empty";
}
if (isset($_POST['uname']) && !empty($_POST['uname'])) {
$request['username'] = $_POST['uname'];
} else {
$error[] = "Username is empty";
}
if (isset($_POST['pass']) && !empty($_POST['pass'])) {
$request['password'] = $_POST['pass'];
} else {
$error[] = "Username is empty";
}
$request['lang'] = 'en';
$request['apiUrl'] = '127.0.0.1';
if (count($error) > 0) {
echo implode( "<br>" , $error );
} else {
try{
$res = $api->remoteCall('requestLogin',$request);
} catch ( Exception $e ) {
print_r($e);
exit();
}
}

PHP Static var not working

I'm working on a Captcha class and i'm almost done, there is one thing that doesn't work
In the file where I put the form, I start with this line:
include 'captcha.php';
$captcha = Captcha::tryCaptcha(2,4,'#000', '#ffffff');
and this is the captch construct:
static $do_generate = TRUE;
function __construct($aantal_letters = 2, $aantal_cijfers = 4, $voorgrond = '#000000', $achtergond = '#ffffff') {
session_start();
if (self::$do_generate == TRUE) {
$letters = substr(str_shuffle('ABCDEGHJKLMNPQRSTUVWXYZ'),0 ,$aantal_letters);
$cijfers = substr(str_shuffle('23456789'),0 ,$aantal_cijfers);
$imgbreed = 18 * ($aantal_letters + $aantal_cijfers);
$_SESSION['imgbreed'] = $imgbreed;
$_SESSION['captcha'] = $letters . $cijfers;
$_SESSION['voorgrond'] = $this->hex2rgb($voorgrond);
$_SESSION['achtergond'] = $this->hex2rgb($achtergond);
}
}
so in other words I put my stuff in a session if the static var $do_generate == TRUE
So when I post the form, the captcha is getting checked by a procesor.php
like this:
if (Captcha::captcha_uitkomst() == TRUE) {
echo "Great";
} else {
echo "Wrong";
}
And this is the captcha function that checks the etered captcha code:
static function captcha_uitkomst() {
if (strcmp($_SESSION['captcha'], strtoupper(str_replace(' ', '', $_POST['captcha-invoer']))) == 0) {
return TRUE;
} else {
echo "test";
self::$do_generate = FALSE;
return FALSE;
}
}
If I enter a correct captcha code, it's all good, that works I get the echo great.
If wrong I get the echo Wrong,
Perfect, but.... when I go back to form (hit backspace one history back) to enter a correct captcha, it regenerates a new captcha.
In the class: captcha_uitkomst you see that I made the self::do_generate FALSE
And the echo 'TEST' works when it's false, (just for checking)
What am I doing wrong
When you hit "back", the page is reloaded. You get a new CAPTCHA.
The premise of your question is fundamentally flawed, as you have just randomly assumed that this shouldn't happen, whereas in reality this is entirely by design.
It wouldn't be a very effective CAPTCHA if you could repeatedly get it wrong then go back and try again; any bot could just start brute forcing it and learning from the experience.

Yii::app()->lang doesn't work sometimes with LimeSurvey

I am working on a custom script to automatically send out invites and reminders. I have everything working fine up until a point. My function to send invites looks like this:
function sendInvites($iSurveyID) {
$oSurvey = Survey::model()->findByPk($iSurveyID);
if (!isset($oSurvey)) {
die("could not load survey");
}
if(!tableExists("{{tokens_$iSurveyID}}")) {
die("survey has no tokens or something");
}
$SQLemailstatuscondition = "emailstatus = 'OK'";
$SQLremindercountcondition = '';
$SQLreminderdelaycondition = '';
$iMaxEmails = (int)Yii::app()->getConfig("maxemails");
$iMaxReminders = 1;
if(!is_null($iMaxReminders)) {
$SQLremindercountcondition = "remindercount < " . $iMaxReminders;
}
$oTokens = Tokens_dynamic::model($iSurveyID);
$aResultTokens = $oTokens->findUninvited(false, $iMaxEmails, true, $SQLemailstatuscondition, $SQLremindercountcondition, $SQLreminderdelaycondition);
if (empty($aResultTokens)) {
die("No tokens to send invites to");
}
$aResult = emailTokens($iSurveyID, $aResultTokens, 'invite');
}
I also have a simple little file that starts up Yii:
Yii::createApplication('LSYii_Application', APPPATH . 'config/config' . EXT);
Yii::app()->loadHelper('admin/token');
Yii::app()->loadHelper('common');
Everything works as expected up until I actually try to send emails to the tokens. I've tracked the problem down to the following, on of the functions called by emailTokens has this in it:
$clang = Yii::app()->lang;
$aBasicTokenFields=array('firstname'=>array(
'description'=>$clang->gT('First name'),
'mandatory'=>'N',
'showregister'=>'Y'
),
The Yii::app()->lang part seems to be causing issues because then php is unable to call the gT method. However, when LimeSurvey is running "properly" this never happens. I can't even seem to find where "lang" is in the LimeSurvey source.
What can I do to make it work?
Why do you make it so hard on yourself and not use the RemoteControl2 API ?
See http://manual.limesurvey.org/wiki/RemoteControl_2_API#invite_participants
On that page you will also find a PHP example script.
maybe
Yii::import('application.libraries.Limesurvey_lang');
$clang = new Limesurvey_lang($oTokens->language);

Categories