Laravel send multiple posts but save only the last one to database - php

In Laravel I'm developing a web app where users will answer true or false questions on separate pages. On each page I'd like to somehow save their answer and in the end save the results to the database.
I could make a POST form on each page which would increment 'points' field in the database if the answer was correct. But that would require many accesses to the database by each user as answers would be saved on each page. Is there a way to store their points to a variable on the server and then save that variable to the database in the end?
I've thought of saving points to session but that wouldn't be safe as sessions can be modified by users.
Right now my controller only returns the intro page.
class QuizController extends Controller {
public function index() {
return view("quiz.pages.1");
}
public function addPoint() {
$points++;
}
public function getPoints() {
return $points;
}
}
And a route to redirect to the next pages.
Route::get('quiz/{page}', function($page) {
return View::make('quiz.pages.' . $page);
});

The way i would achieve this in my opinion is to send hidden inputs to your pages and not save() them until the end. So it would look something like this.
firstpage.blade.php
<input type="text" name="yourinputname">
So then you can pass this input from your controller to the next page without saving it. Something like:
public function index(Request $request) {
$yourVariablename = new YourModelName;
$yourVariableName->yourDatabaseColumnName = $request->input('yourinputname');
return view("quiz.pages.1", [
'anyName' => $yourVariableName
]);
}
Notice there is no save() on the controller yet because we want to save it at the end so we just store it. From this points onward you can just do something like this in the next pages until you get to the point where you want to save.
anotherpage.blade.php
<input type="hidden" name="yourinputname">
When you gather all the data, just match them with your database columns and then save() it. I hope it solves your problem.

Related

Properly cache a type-hinted model in Laravel

I'm using Redis to cache different parts of my app. My goal is to not make a database query when the user is not logged in, as the app's content don't get updated regularly.
I cache the archive queries in my controller, however when I type hint a model in the controller, the model is retrieved from the database and then passed to the controller:
// My route
Route::get('page/{page:id}', [ PageController::class, 'show' ] );
// My controller
public function show ( Page $page ) {
// Here, the $page will be the actual page model.
// It's already been queried from the database.
}
What I'm trying to do is to try and resolve the page from the cache first, and then if the cache does not contain this item, query the database. If I drop the Page type-hint, I get the desired result ( only the id is passed to controller ) but then I will lose the benefit of IoC, automatic ModelNotFoundException, and more.
I've come across ideas such as binding the page model to a callback and then parsing the request(), but seems like a bad idea.
Is there any way to properly achieve this? I noticed that Laravel eloquent does not have a fetching event, which would be perfect for this purpose.
You can override the default model binding logic:
Models\Page.php
public function resolveRouteBinding($value, $field = null)
{
return \Cache::get(...) ?? $this->findOrFail($value);
}
Read more here https://laravel.com/docs/8.x/routing#customizing-the-resolution-logic
In order to check for existence of the data in Redis, you shouldn't type-hint the model into the controller's action. Do it like this:
public function show($pageId) {
if(/* check if cached */) {
// Read page from cache
} else {
Page::where('id', $pageId)->first();
}
}

PHP MVC Controller Created Over and Over Again

I am new to PHP. I am using a MVC project as an example, and I noticed that each time a submit button is pressed my controller is called. The issue with this is that it creates a new model every time the button is pressed.
To fix this, I used a hidden field to check if the button has already been pressed. If it has, then I do not instantiate a new Model, otherwise I do. Code from controller is below:
//code listed below is in the controller which is called each time button
//has been pressed...
$myModel;//used to access model and its functions from controller
if(isset($_POST['has_started']))
{
//stores some logic that uses $myModel variable
playingGame();
}
else
{
echo "just starting...";
$myModel=new HangManModel();
startGame($myModel);
}
This seems to work, BUT then when playingGame() is called it tells me I cannot use $myModel and its functions because it was not declared, or it is a non-object. How can I fix this? Thanks for the help!
First, there is no mvc pattern I see here.
Secondly, php executes and generates the output in html and javascript and sent to the browser.
At the next postback, php does not store any state for this.
As per my understanding, for storing states you can use one of the following.
1) Option1-->Using session.
//code listed below is in the controller which is called each time button
//has been pressed...
$myModel;//used to access model and its functions from controller
if(!isset($_SESSION)){session_start();}
if(isset($_POST['someuniqueID']))
{
//stores some logic that uses $myModel variable
$someuniqueID = $_POST['someuniqueID'];
$myModel = $_SESSION[$someuniqueID]; //retrieving the session object.
playingGame($myModel); //passing your model to the main function.
}
else
{
echo "just starting...";
$myModel=new HangManModel();
//start session
$_SESSION['someuniqueID'] = $myModel; // do not store very complex model object to avoid server memory problem.
startGame($myModel);
}
2) Option2--> you can serialise that object and send it in hidden field then it will be posted back but this will have bandwidth consumption problem. Each time, the page is posted, it will send your model back and forth.
3) Option3--> If your model is big, Use database and create a temp table where you can store unique id and serialised $myModel object. You will need to clean the unwanted states from database. because unlike session and hidden field it is permanent storage.

CakePHP - Controller or No Controller?

I am currently building a web app which has two models, Donor and Donation Models respectively. It has multiple user roles. When the staff user first registers a donor, I want him to be redirected to another form which allows him to fill in the Donation details(the donor is registered once the first donation is successful).
Firs of all, should I create a donation controller, from which I would redirect the user using:
return $this->redirect(array('controller'=>'donations','action'=>'add'));
For the above to work, it requires me to save the newly registered donor's id in a session like so :
$this->Session->write('id', $this->Donor->id);
So the user is redirected to 'donations/add' in the url, and this works fine.. However I think this has some flaws. I was wandering whether I should create another action inside the Donor controller called 'add_donation', which will have its respective 'View'. The idea is to be able to form a url of the sort : 'donors/add_donation/4' (4 being the donor_id ! )
This URL follows this construct: 'controller/action/id'
If anyone could shed some light on best practices, or describe any caveats to my solution(the former, using session etc.) , please do help a brother out! Ill be deeply indebted to you! Thanks in advance!
After you saved the data you can do this in the DonorsController:
$this->redirect(array(
'controller' => 'donations',
'action' => 'add',
$this->Donor->getLastInsertId()
));
There is no need to return a redirect, it's useless because you get redirected. Notice that we pass the last inserted record id as get param in the redirect. The redirect method of the controller calls by default _stop() which calls exit().
CakePHP3: There is a discussion about changing that default behavior in 3.0. Looks like in CakePHP 3.0 the redirect() won't exit() by default any more.
DonationsController:
public function add($donorId = null) {
// Get the donor to display it if you like to
if ($this->request->is('post')) {
$this->request->data['Donation']['donor_id'] = $donorId;
// Save code here
}
}
I would not use the session here, specially not by saving it to a totally meaningless and generic value named "id". If at all I would use always meaningful names and namespaces, for example Donor.lastInsertId as session key.
It's not always clear where to put things if they're related but the rule of thumb goes that things should go into the domain they belong to, which is pretty clear in this case IMHO.
Edit:
Leaving this edit here just if someone else needs it - it does not comply with the usage scenario of the asker.
If you have the user logged in at this stage, modify the add function to check if the userId passed is the same as the one logged in:
DonationsController:
public function add($donorId = null) {
// Get the donor to display it if you like to
if ($this->request->is('post')) {
if ($this->Auth->user('id') != $donorId) {
throw new InvalidArgumentException();
}
$this->request->data['Donation']['donor_id'] = $donorId;
// Save code here
}
}
You can use also the same controller using more models with uses.
Or you can also to ask to another controller with Ajax and morover to get response with Json.

Detecting changes in the model; php yii framework

I'm creating an audit trail module that i will put in a larger system; and i've created a table to store the trail entries , as an "auditor" what i want to see the currently logged on user, the page where he/she is in, what action he/she did, and what were the changes and when...
these are basically what i want to see; my audit trail table looks like:
User| Timestamp| Module Name| Action| Old Value| New Value| Description
i basically had no problem getting the user, by
Yii::app()->session['username'];
the page/module and action by getting the controller's :
$this->module->getName();
$this->action->id;
My problem lies with the changes old value to new value, the edits done by the user.
i could sort of "sniff" out what edits/ changes he/she did by literally copying the variables and passing it through my function where i create the log.. How do i do this dynamically?
i sort of want to detect if a certain model's properties or attributes has been changed and see what changes were made so that i could get a detail log...Thanks ! sorry, i'm really trying hard to explain this.
In each model that you want to observe you can write a afterFind() method, where you store the current DB attributes into some private variable, e.b. _dbValues. Then in beforeSave() you verify the current attributes with the ones in _dbValues and create an audit record if there was a change.
After you have this working, you can take it a step further and create a behavior from it. You'd put the private variable, the afterFind() and the beforeSave() method there. Then you can attach that behavior to many records.
Quick example:
class Book extends CActiveRecord
{
private $oldAttrs = array();
public static function model($className = __CLASS__)
{
return parent::model($className);
}
public function tableName()
{
return 'book';
}
protected function afterSave()
{
// store history
if (!$this->isNewRecord) {
$newAttrs = $this->getAttributes();
$oldAttrs = $this->getOldAttributes();
// your code
}
return parent::afterSave();
}
protected function afterFind()
{
// Save old values
$this->setOldAttributes($this->getAttributes());
return parent::afterFind();
}
public function getOldAttributes()
{
return $this->oldAttrs;
}
public function setOldAttributes($attrs)
{
$this->oldAttrs = $attrs;
}
}
Your solution is good, but what if there're 2 threads that call ->save() at the same time?
Assume that:
the 1st thread find record, save the A status.
the 2nd thread find record, save the A status.
then 1st thread change record to B, call ->save(). System will log A->B
then 2nd thread change record to C, call ->save(). System will log A->C
summary, there're 2 log: A->B, A->C. If this is not problem for you, just ignore it and do the above solution.

Please suggestion a better design of this controller class

Here is the code using CodeIgniter:
The problem I encounter:
The controller will have some functions call view, and it
separated, but it is still very close with the logic itself, if the
controller change to return in JSON or XML to display result, it seems
very trouble.
Seems many method, but each one is depends another.
I think it is difficult to track the code.
Please give some suggestions thank you.
*Please reminded that, it is only the controller class. the load view is actually prepare the data for the view, won't render the page. also the doXXX function call model is only use the model method, it won't have any SQL statement. The MVC is separated, but the controller also have the functions related to the view or model, make it quite messy.
class User extends CI_Controller
{
public function register()
{
//check is logged in or not
//if not logged in , show the register page
}
public function show_register_page()
{
//generate the UI needed data , and call the view to render, and will the user will post back a valid_register function
}
public function valid_register()
{
//do all the valid logic, if success,
//do the do_register
//if fail, valid_register_fail
}
public function valid_register_fail()
{
//check is logged in or not
//show the valid register fail page
}
public function show_valid_register_fail_page()
{
//generate the UI needed data , and call the view to render
}
public function do_register()
{
//insert data in the db, the Model will be called
//if something go wrong in db, show the error page
//if everything is success, show the register success
}
public function show_db_error_page()
{
//generate the UI needed data , and call the view to render
}
public function show_register_success()
{
//generate the UI needed data , and call the view to render
}
}
1. The controller will have some functions call view, and it
separated, but it is still very close with the logic itself, if the
controller change to return in JSON or XML to display result, it seems
very trouble.
Depends on how you organized your code and what you actually pass into the view (template). If that's well structured, you can have one view for HTML, one for XML and one for json, where-as json normally just encodes the view variable's (see json_encodeDocs).
2. Seems many method, but each one is depends another.
Well, just don't do it :) The names look like you wanted to "code that into". Keep it apart. Make those function actually actions that a user performs:
register - that action handles the registration process
Make a login controller out of it that handles anything you need:
login - the login action
lost_password - the lost password action
register - the registration action
activate - the registration activation action
Everything else does not belong in there. There is no need for an action to display some page - the controller itself can decide which view to pick.
Next to that you don't need to display database errors. CI takes care of that. Just put only in what's needed and keep things simple. That should help you to reduce the number of methods and the code therein as well.
3. I think it is difficult to track the code.
Sure. Too many functions with not really speaking names. Keep things simple. It's not easy, but give naming and reducing the overall logic some love.

Categories