I have a problem that whenever I update a paper it will always error out Unknown variable [Fname]
This is the update function in my controller
public function update(Request $request, $PaperID)
{
$request->validate([
'PaperTitle' => 'required',
'PaperType' => 'required',
'file' => [
'required',
File::types('pdf')
->max(12 * 1024),
],
]);
$paper=Papers::find($PaperID);
$file=$request->file;
$filename=time().'.'.$file->getClientOriginalExtension();
$request->file->move('assets', $filename);
$paper->file=$filename;
$paper->PaperTitle=$request->PaperTitle;
$paper->PaperType=$request->PaperType;
$paper->College=$request->College;
$paper->DateCompleted=$request->DateCompleted;
$paper->ContentAdviser=$request->ContentAdviser;
$paper->update();
$input = $request->all();
if(count($input['Fname']) > 0){
for($i = 0 ; $i < count($input['Fname']) ; $i++){
$author->Fname = $input['Fname'][$i];
$author->Lname = $input['Lname'][$i];
$author->update();
}
}
return redirect()->back()->with('success','File has been updated.');
}
the storing works fine but update doesn't how to fix this? I want that both papers and authors table will update the papers updates fine but the author does not
Solution
Use $paper->save() — update expects different params.
Try this for updating:
$paper->update($request->validated()->except('file'))
// or
`$paper->update($request->validated()->only(['field1', 'field2', 'etc'))`
(of course, you'll want to add validation for all Model attributes)
Extra credit (PR Review Learning Comments)
1. Code Readability
First, add spaces around your = & after your if's. Better yet, use a library (like php-cs-fixer) to automatically format your code.
2. Use a custom UpdatePaperRequest
Using a custom Request rather than using $request->validate() helps organize your code. Laravel delegates validation to requests. You can then test the Request separately, create reusable/shared functionality (Traits/Concernst), etc... it's just better trust me :)
Related
I'm getting data from an outside API. What's the cleanest way to delete existing records that no longer appear from the API after using updateOrCreate? Right now, I have a working method which gets the collection before and forgets the collection record on the API loop. Afterwards, it deletes anything that's left.
This feels hacky and I'm sure there's a better solution. Thanks in advance!
Using: Laravel 7.x
$apiPosts = api('....')->json(); // array
$existingPosts = Post::get();
foreach ($apiPosts as $post) {
Post::updateOrCreate(['id' => $post['id']], [
'title' => $post['title'],
'body' => $post['body'],
'is_active' => $post['post_visible'],
]);
// Works but ugly
if ($existingPosts->where('id', $post['id'])->first()) {
$existingPosts->where('id', $post['id'])->first()->forget();
}
}
// Works but ugly
foreach($existingPosts as $post) {
$post->delete();
}
I have been trying to get cakephp to suggest input from data that is from my tables like autocomplete. I've done some reading about how some other people have done this but still can't figure it out. Currently it seems that every time my controller is waiting for an ajax request and it is always false. No errors come up from the console some i'm not sure what i'm doing wrong. I tried removing the if ($this->request->is('ajax')) statement but then I get a error about it cannot emit headers.
Here is my search function in InvoicesController which I have taken code from someone else example but failed to implement it.
public function search()
{
if ($this->request->is('ajax')) {
$this->autoRender = false;
pr('b');
$name = $this->request->query['term'];
$results = $this->Invoices->find('all', [
'conditions' => [ 'OR' => [
'id LIKE' => $id . '%',
]]
]);
$resultsArr = [];
foreach ($results as $result) {
$resultsArr[] =['label' => $result['full_name'], 'value' => $result['id']];
}
echo json_encode($resultsArr);
}
}
And here is my search.ctp
<?php use Cake\Routing\Router; ?>
<?php echo $this->Form->input('id', ['type' => 'text']);?>
<script>
jQuery('#id').autocomplete({
source:'<?php echo Router::url(array('controller' => 'Invoices', 'action' => 'search')); ?>',
minLength: 1
});
</script>
This is my invoice table and the ids are what I want to be suggested from what users type in.
I may not be seeing your exact problem but let me point out a few things I see that might help this issue.
Remove this line. It is not necessary
$this->autoRender = false;
Instead you should be doing this at the end. See using the RequestHandler
$this->set('resultsArr', $resultsArr);
// This line is what handles converting your array into json
// To get this to work you must load the request handler
$this->set('_serialize', 'resultsArr');
This will return the data without a root key
[
{"label":"Label Value"},
{"label":"Another Label Value"}
]
Or you can do it like this
$this->set('_serialize', ['resultsArr']);
This will return data like
{"resultArr":[
{"label":"Label Value"},
{"label":"Another Value"}
]}
Replace your finder query with this.
$resultArr = $this->Invoices->find('all')
->where(['id LIKE' => $id . '%'])
// If you want to remap your data use map
// All queries are collections
->map(function ($invoice) {
return ['label' => $invoice->full_name, 'id' => $invoice->id];
});
It seems to me you might want to review the new cakephp 3 orm. A lot of hard work went into writing these docs so that they could be easily read and relevant. I'm not one to push docs on people but it will save you hours of frustration.
Cakephp 3 ORM documentation
A few minor things I noticed that are also problems.
You never define $id.
You define $name but never use it.
pr is a debug statement and I am not sure why you have it.
Based on your comment, here is an update on ajax detection.
// By default the ajax detection is limited to the x-request-with header
// I didn't want to have to set that for every ajax request
// So I overrode that with the accepts header.
// Any request where Accept is application/json the system will assume it is an ajax request
$this->request->addDetector('ajax', function ($request) {
$acceptHeaders = explode(',', $request->env('HTTP_ACCEPT'));
return in_array('application/json', $acceptHeaders);
});
First of all, I am not familiar with Laravel so much (or with the term "dirty" for that matter).
I stumbled upon this line of code -
if ($this->isDirty('status')) {
if (Notification::has('website-status-' . strtolower($this->status))) {
Notification::set($this->account, 'website-status-' . strtolower($this->status), $this->emailAttributes())
->email();
}
}
And I couldn't understand what that means exactly. I tried to find out on the internet but the Laravel site only says this
"Determine if a given attribute is dirty"
which doesn't really help...
When you want to know if the model has been edited since it was queried from the database, or isn't saved at all, then you use the ->isDirty() function.
The isDirty method determines if any attributes have been changed since the model was loaded. You may pass a specific attribute name to determine if a particular attribute is dirty.
$user = User::create([
'first_name' => 'Amir',
'last_name' => 'Kaftari',
'title' => 'Developer',
]);
$user->title = 'Jafar';
$user->isDirty(); // true
$user->isDirty('title'); // true
$user->isDirty('first_name'); // false
Eloquent provides the isDirty, isClean, and wasChanged methods to examine the internal state of your model and determine how its attributes have changed from when they were originally loaded.
You can find complete description and examples of these three methods here in the official document:
https://laravel.com/docs/9.x/eloquent#examining-attribute-changes
As support for the accepted answer:
$model = Model::find(1);
$model->first_column = $request->first_value;
$model->second_column = $request->second_value;
$model->third_column = $request->third_value;
if($model->isDirty()){
// the model has been edited, else codes here will not be executed
}
$model->save();
In order to manage concurrency - that is ensuring that data being saved to the database is not stale or already edited by some other user - in my CakePHP application I am using the modified attribute in my edit functions. Below is a snippet of the code that is in my controller.
$this->MyModel->recursive = -1;
$event = $this->MyModel->findById($id);
$requestTimeStamp = new DateTime($this->request->data['MyModel']['modified']);
$dbTimeStamp = new DateTime($event['MyModel']['modified']);
if ($requestTimeStamp < $dbTimeStamp) {
$response = array(
'success' => false,
'id' => $id,
'message' => 'A concurrency error occurred while trying to save. Please try again');
echo json_encode($response);
exit;
} else {
//... continue processing
}
This code works fine - but as I try to optimize it across my application I am trying to figure out where best to place it. Is it best placed in my AppModel class or is it better to create a Behavior for the same or is it just best left in the controller? I suppose that an ideal option would consider performance and minimize the amount of class loading overhead as well as database access overhead.
Has anyone come across / solved this problem before? Thoughts / suggestions appreciated.
So I solved this by making concurrency check a part of my AppModel->beforeSave() method. Below is the code for reference of others
/*
* Incorporated concurrency check in the beforeSave callback method to ensure that data is not stale before user saves.
* The function checks if the model has a `modified` field, before it proceeds. If the model does not have such a method
* then concurrency does not apply to this data structure. Upon proceeding, the method checks to see if the value of modified
* data is the same in the database as well as the request that invokes this method. If they are not same then the save is
* aborted
* This method requires the view or controller to pass a variable called data[ModelName][modified].
* This variable must contain the value of the modified field when the record was read and it must be passed back as such.
* I usually set a hidden form field in my view like below -
* <input type="hidden" name="data[Model][modified]" value="<?php echo $model['modifed']; ?>" />
*/
public function beforeSave($options = array()) {
if ($this->hasField('modified') && isset($this->data[$this->name]['id']) && isset($this->data[$this->name]['modified'])) {
CakeLog::debug('AppModel: beforeSave - inside concurrency check');
CakeLog::debug($this->data);
$this->recursive = -1;
// run a select statement to ensure the modified date in the database has not changed. If it has changed then
// the below find query will return 0 rows
$row = $this->find('first', array(
'fields' => array(
'id', 'modified'
),
'conditions' => array(
'id' => $this->data[$this->name]['id'],
'modified' => $this->data[$this->name]['modified']
)
));
// if no row is returned then error out and return - basically a concurrency error has occurred
if (!$row) {
CakeLog::error($this->name.':Concurrency error - [row-id:'.$this->data[$this->name]['id'].']');
return false;
}
// if a row was retrned then there is no concurrency error, so proceed but change the modified date
// to current timestamp to reflect accuracy
$this->data[$this->name]['modified'] = date('Y-m-d H:i:s');
return true;
}
}
I edited the 'comment' table in my Drupal MySQL database to add two rows. This is because I have a page that takes in a URL parameter, so while there is one page, I need to distinguish between the values of that parameter for comments. I'm having trouble editing my comment.module to edit the MySQL query. I can't find any kind of 'INSERT into...' query anywhere, not just in that file. I've looked through everything in the comment module folder.
What appears to be what affects the database insertion is the comment_publish_action() function in comment.module but I'm still running into some problems regarding the added columns, as they don't have default values.
Here's that function, 'typenode' and 'idofnode' are the added columns with test values:
function comment_publish_action($comment, $context = array()) {
if (isset($comment->subject)) {
$subject = $comment->subject;
$comment->status = COMMENT_PUBLISHED;
}
else {
$cid = $context['cid'];
$subject = db_query('SELECT subject FROM {comment} WHERE cid = :cid', array(':cid' => $cid))->fetchField();
db_update('comment')
->fields(array(
'status' => COMMENT_PUBLISHED,
'typenode' => 'player',
'idofnode' => 1239
))
->condition('cid', $cid)
->execute();
}
watchdog('action', 'Published comment %subject.', array('%subject' => $subject));
}
Edit comment.module is not good idea. During next core updates all changes will be lost. Better to create a custom module and implement some hooks there.
There is function comment_save($comment) which perform steps to insert / update new comment. In this function you can find a line drupal_write_record('comment', $comment); which do insert or update of db table 'comment' (dependence on logic). But before this line there is hook module_invoke_all('comment_presave', $comment); which allows you to modify $comment object before it will be store in database. This is good way to go - implement this hook in custom module and do modifications there.
function custom_module_comment_presave($comment) {
//add rows info here
}